 All right, we are a little bit late and I apologize for that, but we are live and we are recording. Welcome everybody who has joined. Welcome everybody who is busy joining. I am a little bit late today because I was rushing to get back after watching my son play a cricket match, which started about 20 minutes late so I apologize. But we're only a few minutes late so it's all good. As you join, as always, if you would like to get your local WordPress environment ready, you're going to need local WordPress installation. You're going to need a code editor of some kind if you want to code along with me. And then our insecure plugin, those of you who were here for the original plugin security workshop that I did at the beginning of this year, you'll notice that the URL is the same, the URL is the same, but you'll need to re-download this plugin because I've made one or two small changes. I've included a few more vulnerabilities. Adrian says, I hope you did well. Yes, the team won. So that was a good result. They're not the strongest team in the squad. So this was their first match of the season and the season is almost over. So they may or may not have another match just because there are so many children in school that play cricket and not so many other schools. But yes, they won their match. So that was very exciting. Always fun to watch. Yes, as I was saying, if you don't have the plugin and you'd like to download it, please do so now that's the URL I will share it in the chat. Where are you from? Where are you joining us from? What the weather is like? It's blistering 32 degrees Celsius today in Cape Town. I have no idea what that works out to in Fahrenheit. Let's have a look. 30 degrees Celsius to, I can't even spell Fahrenheit. I'm going to go Fahrenheit until it says 89. Okay, there you go. 89.6 Fahrenheit, whatever that means. But it's quite warm. Okay. I am going to hide my doc because it gets in the way when I do these sessions. So that's that done. And then I'm going to try and wait for the zoom window to go away so I can close that. And then we can, we can get things going. So I've got my, my local learn press site open in Visual Code Studio already. And I'm going to open my local learn press site here so we can all see it happening. And then we can get going. Okay. Hello, you're all joining and getting set up as always. My name is Jonathan. I'm from Cape Town. I'm a developer educator at automatic. And I'm sponsored to work with the training team. We create tutorials and workshops and lesson plans and courses. And all of that work is hosted on learn.wordpress.org. Today we're going to be doing this is a little bit of a follow up session to the previous session that we had around plug-in security. The previous session we looked at what the sort of five main sort of plug-in security things are that you need to think about when developing plugins. It applies to themes as well. So when you're developing plugins or themes, any, any WordPress product really building sites and if you anywhere where you have custom code where you're busy working with folks working with any kind of inputs and outputs and all those kind of things. So we did that session and I thought it'd be a good idea to kind of look at that again. But from a point of view of actual vulnerability. So let's actually look at the vulnerabilities and understand the vulnerabilities and then learn how to how to deal with them. So that's going to be the focus today. Before we move on a few announcements as always. Thank you all for joining. I don't have a co-host with me today, purely because I forgot to ask someone. Usually I will, I will share my, my workshop GitHub issue in the training team chat on a Monday, and I will ask folks to those who would like to perhaps co-host I'll offer it to folks. And I just completely forgot. So please bear with me if I miss your questions in the chat, I'll do my best to keep an eye on the chat. But please give me some moments if I don't see them straight away. And then one more reminder if you can't see this announcement slide right now so I'm sharing my screen and you should see this announcement slide if you don't see it or if you don't see any of the content today please let me know. And as I might have mentioned before we picked up a bag if you're connecting via a Linux server. And so if you don't see the slide let me know, or any of the content today. We are presenting in focus mode as always which means I can see you all but you can't see each other. If you would like to enable your video you're more than welcome to but there's no need to if you don't want to. And as always you are welcome to ask questions you're welcome to unmute to ask questions or ask questions in the chat. That's my best to answer them as we go through. Okay I'm going to have a sip of my cranberry cooler fruit juice today because it's quite hot and I'm having some fruit juice today, not coffee. While we look at our learning outcomes today so the first learning outcome today is we're going to look at what is known as the oh wasp common web vulnerabilities if you've never heard about oh wasp I'm going to give you a quick introduction to that and what it's all about. And then we're going to look at four common vulnerabilities that happen in WordPress products we're going to look at SQL injection. We're going to look at cross site scripting. We're going to look at cross site request forgery and learn the difference between those two things, and then look at brokers at broken access control. And then there's a little bonus round as well so there's a there's an extra vulnerability that I that I will leave in the code, and hopefully you'll be able to find it. If you don't not not a problem, if you do great, and we'll look at that. And then just looking at our objectives today so we're going to set up and review the insecure plugin. It's the same setup as we've done before so it should be pretty, pretty quick. And then we're going to go through and we're going to look for any SQL injections any cross site scripting vulnerabilities any cross site request forgery vulnerabilities. Any what was the other one I didn't add it to the list here. Any broken access control vulnerabilities, and then find the bonus vulnerability. So we're basically working with the same five things we discussed in the last workshop. But looking at them off from a sort of holistic holistic point of view, looking at the code more holistically what is it all doing how does it all fit together, and looking at the things that we need to work on. Okay, before we start are there any questions around everything that I've just discussed there. If you if you're not sure of anything that I've mentioned or if you have any questions or if you need some time to still set up plugins and things like that. Now's a great time to do it. I'm going to go ahead and download this plugin now so that I have it on my desktop. So I will serve it to my desktop now so that we can install it. And then I'm going to install it onto my website. I think I have it installed currently. No, I do not so let's install it. And I'll upload the file. And where was it here we go learn security. And we will install that. Once the plugin is installed it should add a sub menu to our tools menu. If you're installing this on your local site and you have any of the other example plugins installed on your local site I recommend uninstalling them, just because I tend to use a lot of common prefixes when I'm working with example plugins. This code is not meant to be used in a production environment so I don't recommend following my prefixes but if you have two plugins that I've written for these examples the prefixes might match and you might get some some errors so make sure you've got those uninstalled and just have the WP learn plugin security plugin installed for the session. Alright, so with that plugin installed I'm going to very quickly go through the setup that we need to do if you'd like to join me on setting this up. The first thing is if we click on, if we hover over the tools menu item there's a WP learn admin page link sub menu that we can add. There's still some data from this plugin on my side but basically it's just a bunch of form submissions you might have remembered this. So there's a name field an email field and then a button to delete them. So there I'm going to click on delete and it deletes the form submission, I'm going to just delete these out you probably won't have any form submissions on your side. But you might from a previous session I'm not sure but this is what it basically looks like. Just to quickly show you the database view. It effectively creates a new table in the database called form submissions. There it is over there, and it just has took the structure. It just has a name. Let me zoom in a picture. It just has a name and email field and an ID field. The ID is the unique ID of the field and the name and email it just strings restoring name and email. The other text is pretty tiny so I've made it a bit bigger. Is it still is it big enough now that I need to make it a bit bigger. Okay, so I'm getting positive feedback there. Let me make the admin page a bit bigger as well. Put on 125. Okay, then looking at the actual code and what this does. Let me go and open up the plugins directory and there's the learn plugin security folder. It has a main plugin file. I'm just scrolling to the top very quickly. And effectively what it does is it sets up some constants that we will use later on in the code. It registers and activation. Lisa says, sorry, so small. Is this on the code view that you're talking about now, Lisa? Or was the web view that you're referring to? So if we look over here, is that still too small? Oh, the code view, okay. You shouldn't have to change something your side. It just depends on, I've got mine. I've got my web view maximized to 125%. So let's go to 140. Is that better? Hey, Jonathan. I told her in the comments to go to the top. There's that view option on her screen and she can do a zoom ratio and increase her view. Okay, perfect. I've gone up to 140 now, Lisa. On my screen, does it look better for you now? Can you see the ID name and email fields? The PHP, my admin view is pretty small anyway. We're not going to hang around you a lot. So it doesn't matter too much if you can't see that. I just wanted to show folks what it looked like. I'm going to switch to my code view now. Can you read the code that's on screen? Or do I need to zoom in a little bit there? Okay. Okay, Lisa's fine. Sorry, who was it that gave the suggestion to Lisa? I missed who was saying that. It's Laura. Oh, Laura. Thank you, Laura. Thank you so much. Nice to have you here. It's difficult to tell. I can't tell who's talking. Great. Thank you, everybody. Awesome. Glad we can all see now. Okay. Right. So as mentioned, there are some constants that are being set up. I'll explain what those constants are doing in a second. There's these two constants that are setting up just the URL and the path for the plugin, which we'll use in a second. Don't worry too much about them. Then there is this setup table function, which is effectively the code that creates the custom table. I need you to trust me that that's what it does because we're not going to be looking at that today. This is kind of all plugin setup stuff. So if you've never seen this before, don't stress too much about it. Then there are some scripts being in queue. There's an admin JavaScript file. This basically handles the deletion of the, the records in the backend. And it does some Ajax-y things. So it sets up the Ajax URL here and passes that through to the learn admin script. Then there is the front-end styling. So this includes some styling and I'll show you how this works in a second and why there's a style sheet that is needed. Then the important part is there is a shortcode called WP learn form shortcode all under all underscore. And effectively what that does is that renders a simple form. Anywhere you put that shortcode in a postal page. So it has the name field, the email address field. It has a hidden field that is tied to the form. So we can check if the form is being submitted and it has a submit button. Then there is a callback function hooked into the WP action hook, which is used to process the form. So when you submit the form and the WordPress page that it's on, it's submit and post the request. We check if that form has been posted by checking if that variable is in the post object, sorry, in the post array. And then we get the name and email field. And then we insert those fields into the database into that form submissions table. If the result is successful, then it redirects to the learn WP learn success page slug. And this is where I was talking about those constants that we set up earlier. So I'm just going to scroll to the top and the success page slug and the error page slug are WordPress slugs for pages that we will create in a second. So if you're going to be doing this along with me, you either need to make your slugs the same as mine when you create the pages or just change your slugs in the, in the, in the plugin code. Going back to the form submission, if it fails. So here is the, if it all goes successfully, if the result is greater than zero, in other words, if one record is inserted, then it'll redirect to the success page. If it fails, it'll redirect to the error page. So that's just the way we will set up a yes, this went well or no, this went wrong. Then below that we have the code that handles the admin sub menu, the one that we click on to view all this content of the submissions. And then below that is the callback, the WP learn render admin page callback. So it's tied into the admin page link. And that shows the contents in the admin site. So it shows the list of submissions, the name, the email and the delete button. That's what you saw me deleting earlier. Then there is a function just to get the form submissions. And that's being used inside this admin page. So right at the top here you'll see that I'm calling that function and passing the results into the submissions variable. And then down here I'm looping through the submissions and echoing, echoing out the name, the email and the submission ID to be able to make that functionality work. So that get form submissions just gets all the submissions from the table. And at the bottom, this is the Ajax callback that happens when you click on delete. So in the admin view, when you click on the delete, it makes an Ajax request. And this is the code that runs when that Ajax request happens. It expects an ID to be sent in Ajax request and then it deletes from the table based on where the ID is equal to the ID that we've passed it. For that to work, for the Ajax to work, we need to have some JavaScript. So I'm going to open up the assets admin JS file. And this is basically just some very simple jQuery that hooks into the delete submission button or the link at least. So that's the delete submission class. On the click event, it'll then trigger this code. The code gets the button, gets the button ID from the button data attributes, and then creates a post request using the jQuery post method to the Ajax URL which we're passing to in the in the plugin code. The action it passes is delete form submission and there it passes the ID and the value of the ID. And then once it finishes, then it alerts and says the form submission has been deleted. Now that's a lot of code. And if you don't understand all of it, don't stress too much, but I wanted to show you now how it all works, how it all fits together. And then we'll look at where the security vulnerabilities are. So if you've got this plugin installed and active on your WordPress site, you should now let me just pop back into the code here. You should now be able to use the shortcode, which is let me find it quickly. WP learn form shortcode. I'm going to paste that into the chat. I'm actually going to put it in square brackets because that's what the shortcode should look like. So you should be able to use that in any postal page in your local WordPress site. So I'm going to create a page and I'm going to create it and call it form submission. So just form submission. Now I'm using the classic editor. Sorry, not the classic editor. I'm using the block editor. So I need to add a shortcode block to be able to add this to my WordPress site. And then I can pop the shortcode in there like that. If you're using the classic editor, you can switch to the text view in the editor. Let me actually just show you what that would look like. I've got a, I think I've still got a classic editor installed on my local sense. Let's have a look at admin and password. These are all local passwords. I don't worry. You're not going to be able to hack my life sites with this. Let's just assume this in as well. Okay. And I think I've got the set to classic editor. So let's have a look. Yes, it is set to classic editor. So if you're using the classic editor, you can just click on the text tab. And then you can just again, use the square brackets around the learn form shortcode to use the shortcode in a postal page. Either one would work. Once the shortcode is added to my form submission page, and I publish this page and then view it on the front end. That's effectively what it looks like. It just renders a little form, a name and an email field, or sorry, name and email address field. I can then put some data in there and I can hit submit and it'll submit that data. You'll also note that if we switch back to the code, that it's using the shortcode attributes and it's echoing the class value. Now to show you what that's doing, this is where the style sheet comes in. So I've created very simple, very simple styles for if you apply a blue class to the shortcode, or if you apply a red class to the shortcode, it'll basically just give it a border with a color. And I'll check now about what's happening there, Adrian. I'll check now. Let me just finish the class. And so to get that working, if you want to use the shortcode, so let's go edit page. And what I can do here is I can say class equals, and then I can say red in double quotes and single quotes also works. And if I view this, let me just switch off full screen mode so I can see my view page. There it is. Then there's a little border around so I can say class blue or class red. Okay. Adrian is saying I get a critical error. I get an error when I add the shortcode publishing fail. The response is not a valid JSON response. Okay. So the first thing I just want to check is did you download the link to the plugin that I linked to earlier in the chat? You did. Okay. Great. Let me check my debug log and see if I'm seeing an error because it is possible. Okay. I'm getting something here. It might be a bug in this code. Very possible. So let's delete this. Let me just delete this. Okay. And then we switch back here and let us update this. Let's go back and check. Okay. So it's very possible those debug errors I was getting earlier with related to code I was working on. I do have debugging switched on. So I'm not currently getting debugging errors. So let me just see if I try and submit the form. Let's view this. Let me just see what happens if I try and submit this. Let's say John at gmail.com. Submit this. Okay. So that I am expecting. I'm expecting that to throw me an error. But let's see if I'm getting, I'm still not getting any debug issues. Let me just check the code here. I don't see anything that could be causing any errors for now. So I'm going to move on, but let's swing back afterwards and see if we can help you figure out why that's happening. I'm not seeing anything that would be causing that right now. Okay. So let me set this up so I can get the pages working. And then we'll see if we get any further errors. I got a Jonathan to everyone. I got a warning for a legal string offset class on 18 nine. Let's see here. Line 18 nine. So that's that's probably I know why that is. That's because if the class hasn't been set. Then it's going to give you that error. So if you, if you change it, let's go back a step. If you change your form to remove the class, that's going to throw an error. So let me do that. Let me update that. And then see if I'm getting, there's a debug. So there's the illegal string offset class. So that is a, that is a valid error. So to fix that, you can just add the class to your short code as an attribute. So class equals red or blue. And that should fix that error. Yeah, that is another way to fix it. It's actually fixed the code, but I don't want, the main reason I don't want folks to fix the code too much right now is because it might change line numbers and things. So if you add the class to the short code, that'll also work. And then you shouldn't get that area anymore. So there I'm not getting that area anymore. So if you're getting the illegal string offset error, you can just add the class to the short code in the page. And then you'll get either the red or the blue border. And you shouldn't see that issue again. Okay. Adrian, keep trying if you, if you don't come right, we'll come back at the end and see if we can figure that out. The other thing that I need to set up is I need to create two new pages. And those are the pages that are going to link to the, let me scroll to the top here. The form success page and form error page select. So I need to create a page with the form success page slug. And I need to create a page with the form error page select. Now the nice thing about WordPress is if I create a page with the title form success page and the title form error page, and I don't have any other pages of those titles, the slug automatically gets created for me. So I'm going to do that now. So if I go into my dashboard and I go to my pages, I'm simply going to create a form, hang on, form success page. And I'm going to say thank you just keeping it super simple today. And that's the success. And I'm going to publish that. And you should see if you look at the page address, the page address is simply form hyphen success hyphen page, which is perfect. That's exactly what I needed to be. And then the other page I'm going to create is the form error page. So I'm just going to create a new page and say form error page. Why do I keep doing uppercase where it shouldn't be? Form error page. And then I'm going to go, something went wrong. Please try again. And if I create that, it's going to create the page with the slug form error page. So now when I use my form submission page, and I submit a form, so let's go back and have a look at that. There we go. So the name is, let's just say test. And test at gmail.com. And if we submit that and everything goes well, then it redirects to the form success page and says, thank you, or whatever message we want to have. If we have a look at the database contents now, there's John and test. If we have a look in the dashboard, and we go to tools, and we go to learn admin page, there's John and there's test. Let me zoom us in a little bit more so we can see what's going on there. OK, so besides Adrian, I was having the JSON errors earlier. Is anybody else who's working along with us having any further issues? Adrian got it. OK, awesome. What was the problem? Just so we know so that I can make a note. No clue. OK. All right. Maybe it was some caching issue or something possibly. But if it's working, it's working. That's great. Could be a new page. That's also possible. At least it's working. So that's excellent. OK. I am going to just quickly while we're here. And if anybody else is still busy testing things out and getting things set up and running, I'm quickly going to go over to my repository for this plug-in. And I'm going to very quickly add a note that I need to fix that bug that Jonathan, I think it was picked up. So we're having my own name in the chat. I must admit that was confusing to me. And I'm just going to say that there is a bug if the class attribute is not sent. OK. I'm going to submit that for myself as a reminder. This is what happens when you don't test for the things not being sent in your code. I was working purely with the class being set every time because there was a security vulnerability in doing that. All right. Any questions on how that works, what it does, how it all fits together, or anything else about what we've done so far before we start looking for security vulnerabilities? We don't seem to have any questions at this point. So I think we can move on. All right. So the nice thing about the WordPress documentation is that if you go to developer.wordpress.org and you don't go to the block editor and you go to the common API section. So if you scroll down on that page and you copy this link out quickly, Erin had a question. I'll get to your question in a second, Erin. In fact, let's stop and get to a question. Erin says, how does the form know to call maybe process form after submitting the form? OK. Let's answer that question quickly first. So let me find that page. Let me go and view that page. So the way I'm doing this, Erin, is not actually my recommendation of how to process a page. But it's like a little bit of a cheat that I'm doing, if you will. So when you have a form on any page, and let's have a look at the form code in developer tools, because then we can look at the view and the form code itself on the same time. Those of you who understand or know or have used form forms in web pages before will know that you typically have on the form element. I'm just trying to zoom in on the code. There we go. You typically have a method. So it's either a post. Generally, it's a post because you want to post the data on the form element. And then you will typically also have an action. And the action will be where you are going to send that post request to. So you would typically send it to another URL or a URL to a PHP script on your WordPress site or something like that. What I'm doing is I'm not specifying an action. So when this form gets submitted, the data will just get posted to this page, to the form submission page, this URL here. So it's effectively as good as me saying, action equals learn press dot test slash form submission. What I'm then doing, let me go back to the code here. I can't find it now. In the code execution, let me scroll down and find that. I'm hooking into the WP action. So the WP action, let me show you the list of WordPress codex action list. So this is a list of all the actions that are triggered during a regular sort of request to any kind of WordPress URL. And here we have actions run during a typical request. And these are the actions that are run more or less in this order. And if we scroll down, we will see there is the WP action. And that action is run after the WordPress object is set up. So I'm effectively hooking into that action. And at that point, I am then executing my code. And what I'm doing is I'm checking whether or not this WP learn form hidden field has been submitted. In other words, has somebody clicked on that button? If it has, then process this code. And that's why I'm then also ending execution once I've done my things. So it's a little bit of a hacky way of making a form process. A better way might be to use a REST API endpoint and post to the REST API endpoint or to have a separate URL that the plug-in manages to accept the request and then redirect from there. But because this is just a very simple example plug-in that we're looking for security issues, I'm hooking into the WP action and processing it that way. I hope that answers your question. I wouldn't recommend doing that in a production environment. It's just for me, for example purposes. OK. So getting back to developer.wordpress.org, there is a common API section about halfway down. And if we click on that, it takes us through to all the common APIs. And again, about halfway down, there is a security link. And if you join me for the previous security workshop, you'll remember, we looked through all of this and we worked through the sanitizing data, validating data, escaping data, nonces and user roles and capabilities. But we looked at them very individualistically. So we looked at just what sanitizing data looks like, what validating data looks like, what escaping data might look like, how to use nonces and how to use user roles and capabilities. We didn't really look at it from a, well, these are the common ways. We kind of did cover the common vulnerabilities, but we didn't really look at them in detail. So today we're going to look at them more in detail. Now, before we get to this page here, I want to share another page with you. And this is the open worldwide application security projects top 10 list. And this is a very technical document. So it does take a while to read through and understand, but if you're building any kind of application for the web, so if you're building a plugin, if you're building a theme, if you're building something outside of WordPress, maybe a Laravel application or a KXCMS application or anything Drupal, whatever. It's a good idea to keep this, and they call this the OWASP. And you'll see the little icon as a WASP because the abbreviation is the open worldwide application security project, so OWASP. And every year they do a sort of, I don't know how they do it, they do some kind of research and review and whatever else. I think they work with security companies worldwide and they see what are the top 10 web application security risks that are being logged and being flagged. And you will notice here, for example, from 2017 to 2021, the image is a bit blurry, so I'll talk you through it. But what was the top one in 2017 was injection, is actually now here down at number, where's it gone now? Here it is, number three. And broken access control, which was number five in 2017, is actually now number one, so it changes. So as people are writing code, they become aware of these problems and they write more code to work around them and they go up and down in the list. But the majority of them, as you can see, there's only one to three new ones from 2017. Insecure design, software and data integrity failures and server-side request forgery, those are new. Now server-side request forgery probably comes about because of all of the JavaScript that's going on. And so there's lots of data being passed around in JavaScript applications and therefore server-side request forgery could come in. I don't know about software and data integrity failures or insecure design. And one of the advantages about working with WordPress specifically is a lot of these things, if you follow the principles that we've discussed, you won't have to worry about. Because a lot of the functionality that WordPress already has built in that you should be using to store data as custom post types and using user roles and things will cover most of these things. It's when you step out of the default WordPress functionality as we are doing today. So today we're not using a custom post type to store a form submission. We're creating a custom table and writing our own code to store and delete that data. And that's where we start hitting some of these common vulnerabilities. Okay. Any questions on all of that before we move on? It's a good idea to keep this URL bookmarked and follow it and read it and keep up to date with it, especially if you're building applications. I do recommend keeping this link handy if you want to. All right. I think we can move on. Let's start looking at one of the more common vulnerabilities that we see in WordPress plugins and WordPress themes. And that is on the common vulnerabilities page is the first one, because it is the most common. And that is what's known as SQL injection. If you would like to open up this page on your side and read the comic book, it's an old XKCD comic. It's the one I always reference when we're talking about SQL injection. But effectively, the parents have in this joke, they have named, sorry, what happened there? They have named their child, Robert, single quotes, parentheses, semi-colon, drop table students, semi-colon, slash, slash. And if you entered that as a name in a web application that wasn't preventing SQL injection, it would, and let's say it was an insert into where name equals the value. It would then run that query, and then it would also run the drop query. And if the table happened to be called students, which generally tables are named after what they are being stored, it might delete that table. And as the last panel indicates, we've lost this year's student records. I hope you're happy. And the mom says, I hope you've learned to sanitize your database inputs. My favorite part of this joke is the third panel where the mom says, oh, yes, little Bobby tables, we call them. I just, I just love that nickname. So when we're talking about SQL injection, we're talking about anywhere where somebody can inject SQL code, SQL queries into what you intend to have happen. And to prevent this one should be using the built-in WordPress API. So if you're going to be adding custom posts or posts or whatever you should be using this insert post as a function, which does all the stuff for you in the background. If you're adding post meta, you should be using the add post meta function. When there's a WordPress function used, however, you'll see the further down it says, but sometimes you need to do complex queries or queries that have not been accounted for in the API. In this case, always use the WPDB functions. I see there's a bit of a bug in this. I need to maybe see if we can fix that. But there are a series of functions attached to the global WPDB object that allow you to prevent SQL injections. So I'm going to now ask those of you who have the code open in front of you to tell me how many instances of possible SQL injection vulnerabilities we have in this code. I'm going to give you 30 seconds to do that. So take a look through if you would like to. If you don't have it installed on your site, you can also, this way, let me find my code here. You can also have a look at this PHP file at this URL and you can scroll through it and you can see what you can find there. But have a look at the code and tell me how many you think you can find. And then maybe also give me an idea of where you think, where they are and what we could do to resolve them. You're welcome to unmute if you would like to do that. You're welcome to paste it into the chat. But have a look through the code and see how many possible SQL injection vulnerabilities we have in this plugin. I must wonder whether I should get the sound of crickets playing now. Okay, so Adrian says possibly two on the front and two on the back. Okay. That's a possible answer. Anybody else have any thoughts? If you don't, that's fine. The whole point of today is for us to learn where all this is. So let's see what we can do. Jonathan says I can just see the insert and delete 25 and 202. Okay. Erin says two for sure on line 19 and 20. So let's go through. So let me put in plugin security. Okay. Erin says line 19 and 20. I'm not sure if we're looking at the same code, but line 19 and line 20 in mine doesn't have anything. So maybe you mean, maybe it's 119. Yeah, okay. I think you mean 119 and 120. That's probably more, more valid because I definitely see a problem there. Jonathan said 125 and 202. So there's 125. Yes. That's very possibly an issue. And the other one is 202. 202 is down over here. Okay. And then Adrian said two in the front and two in the back. Okay. Perfect. So yes, there are currently two possible sequel injection vulnerabilities in this plugin. Now Adrian's thinking of it from the point of view of how do we use the functionality? And when she says two on the front, she means possibly the form submission and you're very correct. The form submission is part of it. And you're possibly thinking about the deletion option. That's also part of it. But the actual, the actual code place where the vulnerability happens is in the PHP file. But what's setting it up is on the front end. So you're 100% correct there. So let's just have a look through the code. So some of you mentioned a line where was it? Line 119 and 120. Erin also mentioned line 125. So let's have a look at that code. So there is actually two separate issues in this function, but they form part of the same one issue. Okay. So the first issue is that we're not doing any kind of sanitization of the name and email fields. So if I were to, for example, I'm not going to do it now. But if I were to, in my form submission page, insert Robert, colon, drop table, WP underscore form submissions, it might delete my table. As I say, I don't want to do that because that's going to break my plugin. But I could possibly do that. I could say something like, you know, and it actually wouldn't be on the name field. It would be on the email field. Because if we have a look at the code, the email field is the one that's getting sent in last. So if I was able to go, let's say, Jonathan at gmail.com and then use exactly the same. Let's find Bobby tables. Use exactly the same format. Yes. So email.com, single parentheses, double quotes and then add a drop table. WP underscore WP form submissions. It would run that drop table query or it could run that drop table query. And I would lose my data. So that's the first thing I need to do. I need to sanitize these fields. Now, if we have a look at the, at the section here, they don't talk about data sanitization because it's talking specifically about SQL injection. The SQL injection actually happens here when the insert is being run. But it's possible. It's possible. That if we don't fix the one issue and leave the other issue, it could cause future problems. So we need to kind of think about both. So when it comes to your data sanitization, we're just going to open up the sanitizing data link. And I'm going to paste that into the chat. We did cover this in the previous session, but basically there are functions that you can use to clean your data that's coming in. And they usually start with the word sanitize. And if we scroll down, there's a whole list of functions that you can use. So we're going to start with the data sanitization. And if we scroll down, there's a whole list of functions that sanitize email, file name, hex color, key, meta, mime type, all different things that you can think of, they are sanitization functions for. And if they aren't, you can use the WP KSES and the WP KSES post functions to create your own custom sanitization functions using the built-in sanitization functionality. For our purposes, the sanitize text field will work perfectly for the name. And the sanitize email will work perfectly for the email field. So that's great. We can just use those as it's. So all we need to do is we need to wrap the post name and the post email with the sanitization functions. So sanitize text field. And we can pop the name in there. And then down here, we can go sanitize email and we can do that. So now we're wrapping that code with the correct functions. So now when that data comes into our plugin code, it's going to be cleaned. If anybody inserts anything they shouldn't do, it will get cleaned up before we start using it. Okay. That's the first step in this function. Lisa says, can I use... It's just above... Oh, the actual code itself. Let me pop that in. If I think that's what you meant. So there is the actual code there. Okay. Then we were talking about the SQL query. Now, if we have a look at this query on its own, you will see that I'm basically creating a variable and then I'm just running the query as a string. So I'm just setting up a string with an actual SQL query, SQL query, insert into the name of the table, the two field names, the word values, and then name and email. Now that's a problem. And you might say to me, but why is that a problem? Because you're already sanitizing the data earlier on. Well, the problem is, if I ever came back to this code later on and I added more functionality that perhaps, and this is one obvious, not obvious, one possible example, is I might decide that I want to make the name and email available to other plugin developers to extend, to use, do something with. And I might set up a filter, which for the life of me, I can't remember now. Wordpress, I think it's add filter. Add filter, is it add filter? No, it's apply filters, it's apply filters. So if I'm, when you're working with plugins, you can do something like this. You could say name equals apply filters, and then you can create a filter. So you can say WP, learn, for example, learn, form, name, for example. And then you pass in the name field. And what that does is other plugin developers can come along and they can hook into that filter and they can do something with the name field. Now you're setting this up because you're being nice. You're wanting to make your plugin extendable or your product extendable or whatever the case may be. But you need to remember that other people are not so nice. So somebody might write a plugin to inject nefarious content into your variable using a filter. So you might say, well, the easiest thing is is don't allow filters. Well, the problem is sometimes you need to for whatever purpose to make your plugin work. So you might want to add filters. So whenever you are inserting data into a custom table in WordPress, you should always use the WordPress database object, the WPDB objects, insert function, and not just use the query function and just insert raw SQL. So what that looks like is this, instead of having this insert into table name business, we'll rather have something like rows equals WPDB. And then you use the insert function. Now the way the insert function works, I'm going to just quickly check my notes here. And I can share the link to this in the chat in a second if you would like me to. Its first parameter is the table name. So we can pass in the table name as the first parameter here. And I just do my parameters like this below each other because it makes it easier to read. And then we can add a comma for the next parameter. And the next parameter is a name of field value pairs. So what we can do is we can say array. And the field name is name. So this is name over here. And we use the equals sign greater than sign, assign array values. And then we can pass in name. And we separate that with commas and we say email. And we pass in again email. I will pop this code in the chat. If anybody needs to see it. Here we go. So what that does is that removes the need to do this line of code here. We were writing the raw SQL query. And it also takes away the need to do this WPDB query line. So you can take that away. And then all we need to do is we need to update the result to the rows. It's effectively the same thing. It's just a different variable name. But insert will always return the number of rows inserted. But now we're using a core WordPress function. So it will handle any kind of prepared statements, preparation of the variables, making sure the variables are clean before it gets inserted into the database. Okay. So that's our first SQL injection. I'm going to pause there if anybody has any questions or if anybody's coding along with me. Let me know if you got questions. Otherwise we'll move on to the next one. What do I, what did I mean? Lisa says, what did you remove as code? So what I removed, let's pop it back quickly. So I took out the, I took out the line that said SQL equals inserts into table name, email, name, email values, name and email that line. I took that line out. And then I took out the result equals WPDB query with the SQL past in the brackets out. I took that line out. And then I replaced the results. With rows. So this is what you should end up with. Okay. That's probably because I spelt it wrong. It should be WPDB. Sorry. I've done this before. WPDB insert. So let me pop, let me pop the updated version. Those of you have been around with me for a while. You know, I've done that before. So I apologize. So it's WPDB, not WPBD. WordPress database. Thanks for mentioning that, Adrian. Yes. We've got a call from Adrian. So it looks like it's working on it. I'm actually going to, while we're doing that, I'm going to test my form and make sure my form still works. So let's have a look here. So let's make a Jane and Jane at gmail.com. There. Can't spell today. If I hit submit, that seems to work. And if I check my admin page, Jane has been added. So that's all still working. Great. Okay. Anybody else need me to, to point out that I'm still working. Okay. Anybody else need me to, to pause here? I only ever get the error page rather than success, even when it goes to the database. Is that just a page name issue? It's possible. It's possible that your slugs are not set correctly. So check that your page slugs match one of the two constants to find at the top here. Although I wouldn't spend too much time on that because the whole point is to look at the security side of things. But that's probably, probably the slugs are not. So what you can try and do maybe later if you want to is instead of doing the redirects, maybe, maybe log it to the debug log or echo it out or something and see what, what you're getting in the success and in the, in the error. But if it's storing it to the database, at least you know that that code is working. Okay. Right. Any other questions, any other problems before we move on? I'm happy to wait. If anybody else needs me to stop and wait for something. What I'm also thinking is that if we don't get to all the sessions today, well, sorry, all the sessions, all the vulnerabilities today, it might not be a bad idea to continue this next week. So, so if we do that, I'll think about how we can maybe package up what we have so far. If we get to time package up what we have so far and then have it ready for next week and we can just go on from there. But let's see how it goes. Okay. So nobody else seems to be, be waiting on me. So let's continue. The other one was the one that I think it was Aaron mentioned, and I think someone else mentioned as well. I know Adrian mentioned as well when we delete the form submission in the admin page, there is definitely a SQL injection problem there. And it's the same problem that we had earlier. Number one, we're not validating the ID that's coming in. We're not making sure that it's just the ID number or number at least. And the other thing that we're doing is we're just running a delete query. So again, if something was added to that ID that shouldn't be there, another SQL query and that query was run. Now, fortunately, we're using get results. So if somebody were to add some delete functionality after that one, it may or may not be a problem, but it's still better to make sure that we're using the correct WP, DB delete function. So let's have a look at what that would look like. First of all, in terms of the ID, there isn't a sanitize number function in WordPress. And that's because PHP itself offers a way to sanitize numerical values. There are two ways to do it. The one way to do this is to use the interval function, I think it is. And that's a function that basically just casts the variable to an integer. The other way to do it in the way that I prefer is to just use what's known as PHP casting or type casting, where you actually say, always return this as an integer and you do that by using brackets, open brackets, int for int to close brackets before the variable name. This to me is a much cleaner way of doing it. The interval function is effectively doing this code anyway. So it's a little bit less execution time. So it'd be much quicker. And what that basically does is anytime you pass a value to this post ID. So if you pass in 1, 2, 3, casting it to int or make it an integer 1, 2, 3. If you were to pass 1, 2, 3, ABC, casting it to int would return it as 1, 2, 3. If you were to pass in ABC, it would cast an integer as 0 because it can't find any numerical values in there. So what that means is we need to expect a 0 possibility because we don't want this code to run if it is 0. So what we then should do is something like check if the value of ID, once it's been received from post ID, is greater than 0. So it's 1 or higher. I'm not going to worry about that now, but that's what we should also do. Then the other thing we should do the same as we did earlier instead of having the SQL query set up and using get results. There is a WPDB function called delete. So it would be WPDB delete. And I'm again going to check my notes because I can't remember these things. Let me just find a chair. Yes. So it's effectively the same as insert. The first parameter is the table name. And then the second parameter is an array. And this time the array is the where clause. So where ID equals some value. So you pass in the field. So in this case, it's ID. And then you pass in the ID value. So this works. This works. I don't know why code keeps doing that. This works exactly the same as the inserts. Table name is the first thing. Then the array of field value pairs, but it then does in the background, any kind of preparing of those values that it needs to do, making sure that SQL query is clean and making sure nothing has been tacked on that it shouldn't be. I'm going to post this into the chat quickly now as well. So that folks can grab that. And then we can remove all of this. We'll just, we'll reuse the results. So we'll pop the result back up over here. That's fine. If we just leave it like that. Now we can delete these two lines. So that's one and two. There we go. That's what our final code will look like. So we're setting up the ID, setting up the table name using the WPDB delete option. And then returning the results as a JSON array. Let me check if that works. So if I go into my admin dashboard, I should have a couple of options there. If I delete this one, it says deleted and it has deleted from the database. So the code still works. It's just now way more secure. Okay. I'll pause there for a second. If anybody has questions, comments, things they're not sure of before you run. And I do think this is now going to become a two week session because it's good to go through these things. And we're taking our time, which I think is good. So I'm going to make this a two week session and we'll continue. We'll get through two today. And then we'll do the others next week. So, okay. Right. Nobody seems to have questions. Everybody seems to be happy so far. So let's move on to the next one. If we have a look at that page on the common vulnerabilities page, underneath SQL injection and scroll down a bit. And the next one is cross-site scripting. So cross-site scripting happens when a nefarious party a malicious attacker manages to inject JavaScript onto a web page. Now you might think to yourself, okay, but how does this happen? Well, let me show you a very simple way that this can happen. Let's have a look at the form short code as we are using it right now. So let me go back to my form submission page. And let me edit this page. So the form submission, we're using the short code and we're passing in a class with a value of writ. Okay. And the way short codes work. If we look at the short code in the back end or at least in the plugin code and we find it over here. We receive the short code attributes in this ads variable. I actually don't like ads. I should have made attributes, but it doesn't matter. And then we're simply echoing out the class value in the class attributes on the form. Now, if somebody were to maliciously insert something else into this attribute, that might get echoed out onto the page. Somebody could hook into the short code rendering. So the way that short codes work is it gets sort of stored somewhere and then it renders the short code and then calls the function. And if you, if you haven't set up things so that nobody can attack that code, somebody could inject something else into that attribute, which could output a link to another file or downloads in JavaScript or whatever the case may be. Anytime you are echoing, anytime you see yourself typing the word echo or print. In other words, you are sending data to the browser. You need to make sure that that data is clean. You need to escape that data. Okay. So I've given you one example of where data is being echoed to the screen without being escaped, which is possibly opening me up to cross-site scripting attacks. I would like you to go through this code and see if there are any other places you can find where something is being echoed to the screen and could possibly be attacked using a cross-site scripting attack. You're welcome to give me line numbers. We have made some changes to the code so that the code line numbers might not tie up. Jonathan says line 174175, echo submission name, echo submission email, perfect example. So anywhere I'm using echo, if I'm not escaping that data, that's a perfect example. There is one more. If anybody can find the last one, let me know. I think there's just one more, if I remember correctly. If you find it, let me know, otherwise we'll go through it. Okay. So let's just have a look. There are four areas in this plugin where things are being echoed to the screen that could cause problems. The first one is doing the short code attribute thing. And what I'd like to sort of stop, Adrian says there are four instances of echoes, all of those, absolutely, correct. I'd like us to stop and understand how a simple piece of functionality, like the ability to render the form with a border. That just seems so innocuous. That seems so, how could that be a security vulnerability? But because it is being echoed, based on some dynamic value, it could possibly one day get abused. And the thing to remember is that while this code right now probably is actually fine. Like this code right now probably could not be affected. Again, later on down the line, I might decide some, I might be building a plugin for clients and they're buying my plugin and one client says, Hey, you know, it'd be really cool. If there was a way that you could add a filter to the short code attributes so that I could do something with those attributes and have my own styling. Like to me, that sounds like a really cool feature to give the user. But in doing so and adding that filter, I now open up my code to security vulnerabilities. So then you could say, okay, well, never add filters. Don't ever do that. And the problem is, well, if your business runs on this plugin and you don't do things for your customers, you're going to lose customers. So you have to balance that, making your customers happy versus keeping your plugin secure happy. And the best way to do that is to make sure that your plugin is as secure as it possibly can be. So let's go back to the documentation and remind ourselves about all of the options we have for escaping data. And basically WordPress comes with a whole bunch of escaping functions. The most common one is Escape HTML or ESC HTML. I just call it Escape HTML in my head. And this can be used anytime an HTML element encloses a section of data being displayed. So anytime you're escaping, anytime you're echoing a name, an email, an ID, some content, whatever, Escape HTML is perfect to use. You also have options like Escape JS, Escape URL if you're rendering URLs on the front end and various other options. For our purposes, we effectively just need to use Escape HTML for the submission name and submission email. There is a specific Escape attribute function or ESC attribute function that is used anytime something is printed on an HTML element's attribute. So that will work perfectly for the class. And the reason that you need to use them differently is because they do slightly different things. The one removes slightly more or less than the other and you need to have that when you're working with one or the other. I can't remember exactly which they are now. But it's a good thing to remember that you can't just use Escape HTML everywhere. It's only when it's really content, when it's anything to do with an attribute, then you're going to want to use Escape attribute or ESC-ATTR. Okay. So let's run through them quickly. So the first one, as we mentioned, is in the first line in the WP-LearnForm shortcode function. It's in the Dove where we have the ID WP-LearnForm where we are echoing out the ats class. And all we do is we wrap ats class with that function and we are good to go. So that's the first fix we can make. The next one is further down where the submissions are being rendered. I'm doing this fairly quickly, but I will pause if you need me to in a second. Here we're doing the name and the email. So here we can use the Escape HTML option because this is being rendered inside an HTML tag. In this case, the HTML tag is a table tag or a table cell tag. I've put the quote on the wrong side of that function. There we go. And then again, we can do Escape HTML down over here. There and wrap it around the email. And then we have the submission ID. Now in this instance, we could use Escape attribute because it's in an attribute of the anchor element. That's also that's perfectly acceptable. Or we could again use the integer casting because this is an ID. So if there's anything that was injected into the database, it shouldn't happen. But if it did or into that variable, then we want it to be an ID. So what I prefer to do in this case is again, just to use the integer casting. Again, the reason for that, this is a built-in PHP function. It means it's not running additional code and it's the perfect solution anytime you're working with things that must be numerical values like IDs. Casting to an integer is the perfect solution to that. So those are the four places. So going back up to the top again, the first one was when we were echoing the attributes, the class attributes, we need to wrap that with ESC underscore attribute. And then in the list of submissions, we need to wrap the submission name in ESC HTML, the submission email with ESC HTML, and cast the ID to an integer. Okay. Yes, I'm going to post that code now in a second lender. So the first one is let me post the first one. So let me grab this whole section here. While I'm doing this, I just want to say welcome to Valerie who just recently joined us. So that's how we update the class attributes in the form. And then this is how we update the submission name. I'm going to do submission email just below that. And then I'm going to do the entire ID as well. So I'm just popping them in. I'm not going to do the whole section of code. I'm just doing the different echoes. And again, if you are echoing or printing code to the screen, that code needs to be escaped. So you need to be using either an escaping function or casting it if it's a numeric. If you're not sure read documentation, there are some great examples. Let me actually paste that link as well. There are some great examples of escaping and how you can do it. There's even examples of a custom escaping at the bottom here. So you can use the WP kisses functionality to do your own escaping, which is very cool. And then lastly, to remember that you should always escape late. So the escaping should happen. It's the last thing that should happen to that data. Let's, let's show you what I mean by that. So here we were looping through submissions. You shouldn't, you shouldn't escape up over here. And then later on echo separately, you should always escape late. So it's the last thing that should be done ideally when you are echoing that code. Okay. Any questions on how to escape data, anything about cross-site scripting that you're not sure of? Otherwise we will not move on to cross-site request for tree because that's quite a big one. And I think we'll leave that for next week. We'll move on to the broken access control option. I'm going to take another sip. If there are any questions. Okay. So when we started, I mentioned that we're going to work on cross-site request for tree vulnerabilities is number four. I'm going to jump over that. And I'm going to jump to broken access control. And then next week. We'll focus on cross-site request for tree. And the reason for that is it's quite a big one. It requires a lot of knowledge and understanding of nonces and how they work and how to implement them. So I want to give us enough time to dive into that. The broken access control one I can show you very quickly. And then we'll do the bonus vulnerability next week. And what I'll say is if you want to join next week, maybe have a look and see if there's a bonus security vulnerability, but it's not part of this list of four that you can find and see if you can come back and let us know what it is. Okay. So broken access control is actually not something that is on this SQL, sorry, the on this common vulnerabilities page. And the reason for that is generally when you are using the built-in user roles and capabilities functionality in WordPress, you will, let me actually go straight to that page quickly here. And I'll link it to you in the chat. Generally, you will create a user role like administrator or one of the other options. There's subscriber, there's author, there's editor. And then that user will have capabilities over all custom post-ups or all posts or pages. You generally don't have a situation where somebody can only edit a certain custom post type. Sometimes you might do that, but the built-in roles and capabilities will do all that for you. And I'm actually planning a session on how roles and capabilities work deep dive into how all that works. So that doesn't really help us today when we're talking about when you're building something custom. So I'm not going to ask you where the bag is because we're running a little bit out of time, but we're going to jump right to it. And it's in the learn, delete form submission function, the one right at the bottom of the plugin. Effectively, any user type could somehow trigger this function and then delete these form submissions because I haven't set up any kind of user access control. I haven't said only administrators or only editors or only authors can delete these form submissions. So if I wanted to lock this down to only administrators, I would have to make that happen myself. Now again, because I'm doing this in a custom format, that's why I need to do this. If I was using more built-in functionality, if I was using the built-in custom post type registration support and the built-in capabilities there, it would be easy to configure and it would be just done for me because I'm doing this in a more custom fashion. I'm doing an Ajax request to then delete it. I'm doing custom submenu page. I need to also include some level of user access control. And the easiest way to do that, there's a function in WordPress called current user can. Let me take you to the documentation for that page. I'm noticing that my throat is getting, or at least my breathing is getting a little bit tight. I think it's this juice that I'm drinking, so I'm going to take a break from that. But if we go to the current user can page and I will pop this in the chat when it comes up, there are basically a whole list of capabilities that are predefined in WordPress, in a WordPress install. And they are things like can edit posts or can edit a single post or can edit post meta or can edit pages or can manage options and various other things. If you want to see what that list is, you can go to the user roles and capabilities page in the plugins. And I think they list them somewhere here. I think it's in the codex reference perhaps. I'll find it and share it with you. Here we go. So here is the support article about all the roles and all the capabilities. Let's zoom in there a little bit quickly. Why is my zoom not working? There we go. So it talks about all the different roles, all the different roles you have in a WordPress install, and then which roles have which capabilities. So these are the capabilities. So super admin can create sites, delete sites, manage the network, for example. Administrator can activate plugins, delete pages, delete posts, for example. So if I wanted to make this, this delete functionality available to only administrators, then I would do a current user can check and I would then use one of these administrator only capabilities. And the easiest one to use, I've always found for administrators is the manage options one. Because if I use something like edit posts or delete posts, if we scroll down, you'll see that editors can do things like delete posts and pages and edit posts and pages. So I'm looking for a capability that only an admin user would be able to do. And one of those is the ability to manage options. So what I can do in this plugin code, in this learn delete form submission function, I can write at the top before anything else happens. I can do something like this. I can say if, and I can use the exclamation mark, which means not. So if not current user can, in other words, if the current user can't do this thing, that's the opposite of current user can, and then I can say manage options. So what this, what this check is doing, this is a if statement or if conditional check, it's saying if the current user can't manage options. In other words, they don't have permission to do this, then do something. And the easiest thing to do this is to copy out this return CNJson array result, and then just change the result to something like, invalid user or something. So to return that to the Ajax request. And the nice thing about WordPress, I'm pasting this code in the chat right now. The nice thing about WordPress is just those three lines of code will prevent any unauthenticated users. So only users with administrative privileges will be able to delete these forms. And you can test this. Once you've set up this code, create another user and then try and access these admin submenu page or try and delete it. It actually won't work because I think the admin submenu page is only available. Just check here. Yes, here we go. So when we set up the admin submenu page, we also set it to manage options. So I have some fun with it. You could change manage options here to edit posts, for example, and then allow a creator user that can edit posts, but then try and do a deletion. And you'll see that it should not work because we said only because users who can manage options can delete these records. That's a little bit involved. So we're not going to do that now, but definitely give it a try if you would like to. Okay, I'm going to wrap that up here because I want to give us enough time to deal with a cross-site request for next week, which dives deep into nonces and I want us to really spend time on nonces. Are there any questions around all of the things we've discussed today? Any comments or anything else you want to know? Please let me know now. While you're thinking about that, I would also mention that if you go to... Where is it now? If you go to the Learn Plug-in Security GitHub repository, I have also uploaded and if you go through to the releases page, let me get there now. If you go to the releases page, there is also a version 1.0.2 of this plugin, which is the updated version with all the fixes. So if you don't want to have to come back next week, you can just download that code and you can see how the nonces are being implemented or you can compare it to your current code and see what's being done there. But that's one way you can have a look and see how the code works and how it all fits together. Okay, any other questions or comments before we wrap up here today? Otherwise, I think we're going to call it a day. All right. Hopefully you've learned now how to start looking at your plugin and your theme code and thinking about these common vulnerabilities. Unfortunately, this is a topic that we can't cover in an hour and a half, two hours, three hours, even a day, because it's the kind of thing that you need to be thinking about all the time and you need to really understand how to handle all these things. Lisa says, was the first fix we did for cross-site requests? No, the very first fixes we did were for SQL injection. So those were where we were validating the fields coming in and updating the SQL queries. Okay. What I'm also going to say is, I didn't include this in the slides for some reason, but if you do have any other questions, you can paste them in the comments of this issue for the SQL query. Okay. Thank you. Thank you. You can paste them in the comments of this issue for this workshop and let me know. So if you're working through the code at home, you're watching the video online and you're getting stuck with the code, let me know. Or if you have any other questions about security vulnerabilities or how these things work, you can welcome to add them there. And then next week we will carry on with the cross-site request forgery. The other thing that I wanted to mention is in this, I don't think it's in the workshop. No, it's not. I'm going to share this link with you as well. When I recorded the tutorial for developing a plugin securely, somebody who works with plugins from a security point of view actually shared some additional information with me. And it was the foundation of what this workshop was today. So I wanted to share that with you as well because it's good to read it and understand how these things work. So here is the comment. His GitHub name is Erwan. He works at WP Scan, Jetpack Scan Automatically. It's a company that I work for. And he's actually gone deeper into all the things that I didn't cover in the first tutorial that I was planning for this workshop and also talks about some of the background, some of how it all works and some of the common things that they see at WP Scan slash Jetpack Scan. So the answer to the bonus vulnerability is also in this conversation. So if you want to do that before next week's session, you're also welcome to read through there and we can chat about that. There we have that over there. Okay, great. Thank you all for joining me today. I know that plugin security can be a daunting discussion. It's not something that's easy to do in a workshop, especially when I'm not able to interact with folks. But hopefully you've learned a little bit more about how to work securely with your plugins, how to think about plugin security, the things that you need to be aware of. It's mostly a lot of reading and a lot of understanding and then implementing those things. As I say, next week we'll carry on with this. We'll do a part two. We'll talk about cross-site request forgery. We'll talk about how to use nonces in different places. And then we'll see if anybody wants to talk about the bonus security thing. And then I might also show you another security vulnerability option that I thought about that might be worth it. As you saw from the list earlier, I don't think I have it open anymore. But as you saw from that OWASP list, there are 10, top 10. And we covered maybe five today. So it's a good idea to maybe to understand these things and how they work. Okay, thank you all so much for joining me today. I hope you have a lovely rest of your Thursday and enjoy the rest of your weekend.