 Good morning. Before we begin, I'm going to ask everyone in the room to give a round of applause to the organisers and volunteers of WordCamp US for putting on the event. As Bec said, I work on 10UP's open source practice and they sponsor me to work on WordPress Core for about half my time, where I am a core committer and a member of the WordPress security team. One of the things I try and keep on mind when working on WordPress is that it's used the all sorts of websites. Everything from a completely fictional, and you'll see why it's fictional, family grosses a few minutes from my home to the somewhat less fictional White House a few minutes from where we meet today. For full disclosure, the White House is one of the sites my employer is known for working on. But all that work happened around 12 months before I joined the company, so I have no idea about any of the specifics. I'll be learning about it with the rest of you when the process is discussed later on this afternoon. I'm choosing the White House.gov as an example to talk about because it's, well, it's nearby. We can probably see it if we tried to get onto the roof of this enormous building. But it's also one of those big enterprise builds. For the last eight years or so, it's these big enterprise builds that I've been working on. But in a very real sense, when I am working on WordPress Core, I'm working on sites like this. The local small or solo business that hires a freelancer, designer, developer to build a website for them. In my experience, it's the green family grosses of this world whose sites tend to get hacked. Often the problem comes from a theme or plugging failing to use best practices. To demonstrate this, I've created another fictional site for the talk, the Scarlet Family Grosses. I don't know why, but the green and the scarlet families are sworn enemies. They probably appeared in the game of Clue together. Now, in my imagination, the Scarlet family have ripped off the green family's design. But, you know, for the purposes of a talk, I've got to be a little less subtle. For the big enterprise sites, the Hollywood image of a hacker is somewhat accurate. If you take away the hoodie, the ski masks, the gloves, the metric background, it's people sitting at a computer targeting a particular website, just for the kudos. So the really big sites need to take some steps that not necessarily need to be taken for every site. For example, the White House requires users being on a particular network to access the admin and limit REST API access to these users. They've also gone for a little bit of security through obscurity by removing the generator meta tag and other markup with the version number in it. But if you know where to look, it doesn't take too long to learn that they're still testing WordPress 6.3 and are running the latest 6.2 release. Or at least they were when I checked last weekend. For smaller sites, the steps the White House are taking can help, but they're not as necessary. Rather than individuals seeking kudos, a much more likely scenario is another hacked site attempting to share the love around. Why target one heavily protected site when you can hack thousands of smaller sites in a fraction of the time? It's my firm belief that smaller sites tend to get hacked more often than enterprise sites for one simple reason. A failure to follow best practices or, in the worst of cases, a failure to follow even good practices. In my more frustrated moments, I sometimes think that it's only the big enterprise builds that do follow best practices. And I know in reality this is actually a bit unfair, quite unfair. A lot of freelancers do their best to build secure sites. However, I think a big part of the problem comes from educational resources. Much of the content on science aimed at developers doesn't follow best practices. It's not my goal to call out any of the sites with poor quality code on it. The lack of good quality educational resources is so widespread that if I was to call out one site, I'd need to call them all out. And sometimes, frankly, I feel like that could be a session of its own. That is to say that when I've pulled an example from a website, I've refactored the code so that I can make my point while making it more difficult for you guys to go and Google the source. This code sample comes from the developer resources of a fairly popular plugin. It takes some site-specific metadata from the user's profile and displays it on the front end of the site. These are the lines of concern. They take untrusted user data and output it to the page. There's no consideration as to whether the data is safe or unsafe. And unsafe data can actually be intentionally malicious or, you know, it could be someone making a simple typo. Trusting untrustworthy data is an example of why sites get hacked. Let's revisit our friends at the Green Family Grocers for an example of using untrusted data. The Green Family is savvy social media users and promote themselves on Twitter with links containing special discount codes on their site. The discount code is simply a parameter added to the URL of the website. It sets a cookie and applies the discount to the order. Now, to be honest, because this is a fairly quick demo for a talk, I decided to take a shortcut. I used GitHub's co-pilot, and I just wrote this comment and accepted what co-pilot suggested. As co-pilot reached the end of each suggestion, I added in a comment explaining the next step. And co-pilot was great. It did exactly what I asked. And the keyword there is exactly. If a weary string parameter is set, a cookie is set with the same value. Now, maybe a few people in the room can see where I'm going with this. But please remember what I said a few slides back. Natural language models learn from people. Co-pilots' educational resources are the same as everyone's else's. Code that people have publicly released onto the Internet. For displaying the discount code on the page, I again let my AI friend do that. I've got heavy lifting written here. It's not very heavy. But let's get back to the exciting bit and buy some pairs. Try not to notice the slightly different logo, username, or the awkward typo. The important bit is that the site's URL is correct. It matches the original tweet. And thanks to the truncated display, it appears on Twitter exactly the same. Didn't read that. So, what happened? How did we go from buying new season pairs to giving another site our credit card details? Let's take a closer look at the discount code. Reformatted for clarity, the discount code is a URL encoded in this. The text is the same, WCUS 2023, but there's a nasty bit of JavaScript in there too. On the checkout page, it looks for the credit card form. And if it exists, alters the HTML marker to send those users to the malicious site's page. The issue comes from the line of code I showed earlier, which prints out the discount code on the site. The easiest fix is quite simple and to follow the advice of the WordPress developer resources and always escape your output late. So, the fix for our code is to stop the visitors' credit card numbers from being hacked. It's to escape the output. It's an eight-character function, 10, with the brackets. I mean, it works. Our visitors' credit card numbers aren't going to get stolen, but it's not the kind of thing that's going to inspire confidence. And it also doesn't change the fact that every visitor that follows the link has this stored as a cookie in their browser. It's just sitting there as a time bomb that will cause problems if a developer makes a mistake. And developers have to protect themselves from the future selves. The obvious solution is to sanitize the data before setting the cookie. Sanitizing data just means cleaning up the user input so that it's what we expect. In this case, I'm sanitizing it as a text field, which is a built-in WordPress function that strips out any HTML, including completely removing HTML charts, including JavaScript. And it works. We have a clean discount code containing something that looks like the expected value. I'm so glad we have that sorted. Let's move on. I'm going to discuss the data that is considered untrustworthy. I'm sorry. I really wish we could stop that. This is getting ridiculous. Okay, I left my computer. I was in New York last week seeing shows, and I left my computer unlocked in the hotel room, and I think someone in the house could have decided to have some fun. I mean, we'll fix this, right? We sanitize the data from the user input before setting the cookie. Everything should be fine and dandy. Well, no, it's not fixed. User data is so much more than form fields and query string parameters. Cookies are user data. HTTP headers are user data. Session and local storage values are user data. Everything, pretty much everything that comes from the browser is untrustworthy. Anything stored as a cookie or in local storage from the browser, in most cases, the HTTP headers are controlled by the browser. However, it's relatively trivial for someone to set a cookie in the browser console and even easier to alter the local or session storage. In the example I'm using today, it would be pretty weird to set a cookie in your own browser so that another company can steal your credit card. But what if you worked in a hotel housekeeper hoping to find people who had left their credit cards around or, in my case, their computer unlocked? I mean, you'd have a go. It's important to note that escaping the output has fixed the problem, but it's important to prevent anything unexpected coming from a cookie so we don't end up with an output something like this. It's really trivial to change a cookie's value via the browser console using JavaScript. WordPress even provides a helpful utility file to make it even easier. For things like cookies, which we, the web developer, want to control, I think it's worth going an extra step when checking the data. On the highlighted lines, I'm storing both the sanitized version and the uncentatized version. If those two strings don't match, then it's not a cookie value that was set by the server, and it should be discarded. I've been a little light about the specific escaping and sanitization functions to use, and that's because it's really complex. So you really need to get to know what each function does and there are a lot of them. So you need to do some research. Please do these Google searches. As with the cookie example I use, sometimes you need to compare the user submitted value with the sanitized value. Sometimes you need to take the raw input and compare it with a list of unexpected values. In the discount code example, that would be a list of discount codes, and that's where the nuance comes into it. If you're running code sniffing software, you'll get a warning that this is problematic, but we're comparing it a few lines later as valid input. And that's because the code sniffs don't run code. They look for the best practices. And that's not a limitation of the code sanitization sniffs and in the software. It's an indication that dealing with user input is really complex and needs to be considered on a case-by-case basis. As developers of websites, we don't get to offload what we are responsible to a piece of software. Just as we can't trust AI to write secure code, we can't trust binary software decisions to understand the nuances involved. Enough thinking about visitor input on the front end. Let's look at user input for legitimate trusted users. By default WordPress has four roles that are allowed to post content to the site. There are the high-privileged users, editors and administrators, and the low-privileged users, authors and contributors. The high-privileged users are allowed to use any HTML element they wish, including Adam Kustin JavaScript to a page to show an alert or similar. Authors and contributors content, on the other hand, is run through an HTML filter. That's called cases. This limits their posts to a subset of a loud HTML. Now, this list is still really very generous. I wanted to animate this slide to make that point and ended up crushing keynote. It's generous enough that with enough knowledge to be dangerous, an author can still make a complete mess of things, while being restricted enough that the low-privileged users can't actually harm visitors. Now, if you're running a popular website with hundreds of thousands of visitors and then you've got hundreds of employees maintaining that, some of those employees will require editor or administrator roles, and you probably don't want anyone to be able to harm your visitors. Let me correct that. You definitely don't want anyone to harm your visitors. Adding the disallow unfiltered HTML constant in the WP config file was certainly one of the first things I was done when I worked at the newspaper site. This means that even high-privileged users such as editors and authors can only access the filtered list of HTML. Now, I became friends with the product manager, the former journalist, and I quickly learned something about him. He had really strong feelings that he did not want the website to look like this. There's a hodgepodge of colours, no two sections, share the same font. It's pretty ugly. So even the default set of a layout of HTML tags in WordPress is a little too generous. At the newspaper, our allow list of post tags basically became quotes, links, bold, italics, and lists. We removed the default attributes so people can add style tags and so forth. If you look closely, you'll notice that images are missing from the allow list. This was so we could use custom short codes and later custom blocks to better encourage the use of captions and alt tags. Now, some of you may be thinking it shows a tremendous lack of trust in the users. Surely you're thinking the national editor of a major news outlet can be trusted not to undermine their own credibility by making their own site look awful. And you're right, they probably can. But good security practices not only protect against malicious intent, they protect against accidents. One of my greatest fears while working at the newspaper was confusing the develop and production environments and suddenly announcing that Alexander Hamilton was dead. I was terrified that I'd be logged in as an administrator and published testing content to the testing section. Now, there were reasons I needed to be trusted as an administrator on production, but sometimes I wish I wasn't. And as an aside, for your own personal financial security, when you're writing test content, always presume you are accidentally logged in to production. If you accidentally publish law remits into production, you'll probably get a few grumpy comments. Maybe you'll get yelled at. If you accidentally publish swear remits into your website, you'll be lucky if you're yelled at. You'll probably get an official warning or, in the worst case, get fired. In retrospect, I should have asked for a role at the newspaper that would allow for us developers to administrate the websites but prevented us from publishing new content to production. Ideally, the developer team would have been able to edit existing comments, you know, so we could help our journalists when they're having trouble with the feature but not publish new content to the site. As I said earlier, WordPress comes with a few roles out of the box. Five on a single site installs six on Molyside. And one of those is the subscriber, which comes with only the permission to edit their own profile. If you install certain plugins, you might end up with extra roles. For example, an e-commerce plugin might give you a customer or a shop manager role or a really complex plugin to configure might give you a configurator role. But really, WordPress doesn't have a role system at all. It has a capability system. Instead of checking if the logged-in user is an administrator, an editor, contributor or author when determining if logged-in user can do something, WordPress checks if they are allowed to do a specific activity such as edit a post, install plugins or so on. For big enterprise builds that I've worked on, the built-in WordPress roles have rarely matched the various jobs within the company. The items on the list that went past behind me are called primitive plugins. They're general rules as to whether you can publish posts, activate a plugin or so on. WordPress has what we call medic capabilities. They're used to determine whether you can publish a specific post, activate a specific plugin and so on. Using the White House as an example, you may wish for the vice president's team to give them permission to edit her biography and publish statements on her behalf. But you may wish to present the vice president's team from editing the president's content and publishing statements and press briefings on his behalf. I don't know if this is the case, by the way. I, like most people, learned about the inner workings of the White House by watching VEEP and the West Wing. An approach you might take to this is to add a role for the president's editorial team with some custom capabilities for editing content that's deemed presidential. And I'm taking existing post-editing capabilities and adding the word presidential. You don't need to worry about the code too much. The key bit is that I've inserted, as I said, I've inserted the word presidential. And then if the post the user is editing is deemed presidential, we require the user to have the same capabilities we added early in order to modify the content. And the advantage of this approach is it gives me what I asked for earlier or desired earlier when I worked at the newspaper. When logged in as an admin, I can manage the site without editing the content. Let's talk briefly about hosting your site. Back home in Australia, we have a WordPress-focused social slack called WP Australia. And a common question that comes up is where is the best place to host your site if you want to do x, y, or z? In the WP Australia slack, the common end of the question is serve content to a mainly Australian audience and avoid the enormous latency between the US and Australia. If you want to keep your site secure, there's no one best answer to this. There's a good answer, however, to the following question. Should you just try and save money and configure a VPS yourself and host your site there? Unless you're an experienced system admin, the answer is no. You may be able to save some costs if you team up a VPS with a service that will configure and manage security updates of the actual server packages, like the web server and PHP, but please don't self-configure. In my view, the ideal situation is to not have your WordPress server's IP address available on the Internet. Let's take a look at the 10UP website for an example. If you look at the IP address of our server, you'll discover that we apparently have three of them, three application servers. And if you visit the website, you get this. Well, not this. I got that error message at the time because I changed my browser's user agent string to pretend to be Internet Explorer 6 and a little bit more junk at the end. It's the little bit of junk at the end that got me blocked. It's a very ham-fisted attempt at an SQL injection attack. And browsing the Internet with that set as your user agent results in a much smaller Internet than we're typically used to. And commonly, the sites that you can access look a little bit like the straight out of the 90s. So why are 10UP, NPR and Discord all blocking me from accessing their sites? When you do a look, DNS, look up on their sites, you'll see that they're not seeing the IP address of their application service, the service that actually rendered the website. You're seeing the IP address of their web application firewalls. Into the sites that I visited and included screenshots for, it was CloudFare, Cloudflare providing the application firewall. Basically, they're looking for user input. And in my case, that was the user agent that suggests the browser, the visitor may be trying to hack the site. And it blocks it before it gets to the server. It's not really a snappy headline, but I'd suggest that there's never a really good reason to expose your app WordPress service IP address online. It's one of those features that you may pay as little or as much for as you want. But it is included as a feature within Cloudflare on their free plan. When it comes to keeping your site secure, there's one simple trick you can follow, and that's to remember that there is no single technique that will keep your site secure. If you do nine out of 10 things correctly and securely, the one thing you missed may be enough. When working on a large project, including on WordPress, code review is pretty much baked into the process. It's rare that anything gets merged without a second set of eyes on it. But that's fine on large teams with multiple developers. What do you do if you're a freelancer working alone? And in most situations, the answer is to have an app. Take a break from the code you were writing. Work on another part of the project or switch focus to another client. By taking a break, you have the opportunity to forget about the code you wrote. And then you can review it afresh. When working on WordPress core or other open source projects, I'll often leave my own pool requests overnight and come back to them with fresh eyes the following day. And then I'll request someone else to review it. Often enough, I'm happy with the content of the pool request as I left it the night before. But sometimes I realise I've missed something and it needs fixing. It doesn't matter too much whether it is minor or major. And sometimes it's major. I've missed something major. But I know from personal experience that self-reviewing code after a change of context and focus can be effective. If your work situation means that all you have is self-review, then I suggest you give it a go. Even if you continue working on the same project, you can go back the next morning and review the pool requests you merged the day before. And with that approach, you may save yourself from waking up from your client and saying to yourself, oh no, I've made a huge mistake. Thank you very much. Thank you so much, Peter. So many things to think about. We have some time for questions. There are two microphones in the aisles here and here. Do you want to take a line up if you would like to ask Peter a question? And, um, yeah. Can you? Okay. I would imagine I'm not the only person here that makes smaller websites and needs some best practices for security. I'm thinking things like CDN and what else? I think going through and using a CDN with the web application firewall to block what looks like a malicious attack is one of the best things you can do because it means if you miss something, the CDN with the application might catch it. But becoming really familiar with the escaping and sanitization functions that WordPress provides and if you just Google WordPress common APIs and then just follow that with either sanitization or escaping. I think something, there's something you really need to do is just read and reread. I often find myself on those pages rereading and making sure that I'm using the correct sanitization. Some of them, you know, will strip out the HTML tags. Some of them will, you know, make sure that it only has the characters of a person. Yeah, I think, you know, it's not necessarily easy, but that's something you can do. I was talking about the idea of the code review. Something, you know, if you're a freelancer and building very small sites, often, you know, the WordPress meetup or something like that, you know, in a lot of other sites, you know, there's a tricky bit of code if you are unsure of it. But, yeah, really the idea of sleeping on it can help. So I'm curious, you have, like, a set of standard stuff. Removing the meta tags, maybe changing the admin URL, disable XML or PC. I'm sure you have, like, a standard that you do for every site to keep it consistent. What is, to follow up, how do you keep it up to date as standards change, capabilities change, and if it is, would, is it open source, like what you do or are you willing to do that? So something we have at 10UP is what we call the 10UP experience plugin. And if you go to github.com slash 10UP, you'll find that in the list of our services, the advantage of making it an open source product and sharing it between teams is that any of our client services team, as a new feature is added to WordPress, they, you know, someone can work on that and submit it back as a pull request. But it's something that allows, you know, by making it open source, it allows freelancers to follow up and consider the best practices and something that's a default for all the sites.