We’ve actually already started discussing security when we covered its first and most fundamental layer – user permissions. In this chapter, we’ll go deep into the most common vulnerabilities you can come across in WordPress and how to prevent them.
Common Web Vulnerabilities
Web security is broad. There are entire books devoted just to this topic. It would be impossible and counterproductive to cover it all. As such, I’ll only brief you on some of the most common vulnerabilities you can encounter and that WordPress APIs are supposed to protect you against.
A quick sidenote before we begin: if you’re writing a plugin or any other piece of code, please make sure to first understand what attack vectors your solution is susceptible to. Most vulnerabilities don’t come from programmers forgetting to write protection – they come from programmers not knowing they need protection (i.e., incompetence).
A good first step in that is to just ask an AI chatbot. Let’s say you were writing a plugin allowing users to upload files on the frontend. Explain this functionality to the chatbot and ask about attack vectors. It would quickly give you a list: malicious file upload, path traversal attacks, DoS via large uploads, etc. Whatever you do, just remember the most important rule: don’t be stupid.
XSS
A cross-site scripting (XSS) attack is an attack consisting of injecting malicious JavaScript code on a website for it to get executed in the victim’s browser. This can lead to the attacker gaining access to the victim’s session cookies, theft of data, impersonation, redirection to malicious sites, and more.
Perhaps the most famous XSS in history is Samy, the MySpace worm. It was designed to display the string “but most of all, samy is my hero” and send a friend request to its creator (Samy Kamkar) from the victim’s account. It would also further post the malicious payload on the victim’s profile page, making it replicate itself. If you viewed such a page, the JavaScript would execute in your browser and you’d have become the next victim.
Vulnerabilities like that are caused by not sanitizing user-provided data. Let’s stay in the realm of social media. Every social media has a way to post something, which is basically just a textarea. If the website didn’t sanitize the text you put there, you could just write “<script>alert(‘hi’)</script>”, and every person that viewed your post would involuntarily execute this code.
CSRF
A cross-site request forgery (CSRF) attack consists of tricking the victim’s browser into taking an unwanted action on a website they are logged into. This one is a little harder to explain or understand than XSS, but I’ll try my best.
Let’s say you were logged into a WordPress website example.com. You already know what it means – you have session cookies (e.g., wordpress_{COOKIEHASH}) stored in your browser. WordPress knows it’s you by verifying this cookie.
Now, let’s say someone knows you’re an administrator for this website. They send you a phishing email that looks just like the newsletter you usually read. You click a link from this email, but instead of taking you to the newsletter’s website, it takes you to a malicious website controlled by the attacker.
Remember, every website you open can run any arbitrary JavaScript code in your browser. This one is simple. It sends a POST request to example.com/wp-admin/options-general.php?page=pgn_sett. It just so happens that you’re using a plugin with a vulnerable custom settings page located under that URL.
So what do you think happens next? Well, I’ll tell you. The browser makes a request to the example.com/wp-admin URL and includes all the cookies it has for this path. This means that the wordpress_{COOKIEHASH} administrative cookie will be included in this request to the server, even though it originates from a malicious JavaScript code.
WordPress doesn’t see any difference. It validates the cookie, and if it isn’t expired, it sets the user and the permissions. You’re an administrator, so you can change the options for the plugin. WordPress parses the POST payload (created and sent by the attacker) and saves the changes. The attacker was able to force you to take an action on a website you were logged into without you or them ever visiting this website directly.
A CSRF vulnerability can be prevented by including a unique token in the rendered HTML form and then checking if the POST request contains this token. The attacker doesn’t have access to this token as it’s generated by the server for every form that allows you to take some action (the settings page in our example, which is never accessed by the attacker before making the request).
PS: The example above was based on JavaScript, but a CSRF attack does not require JS. It can be achieved even with basic HTML, like by putting an action link in an image’s ‘src’ attribute. The point is that browsers automatically include cookies, even with cross-site requests.
SQL Injection
SQL injection is similar to XSS, except it happens on the server instead of on the frontend. It consists of the attacker including a rogue SQL query in the request data.
Let’s say you had a search engine on your website allowing users to look for a post by its title. A simplified SQL query would look something like this: “SELECT * FROM wp_posts WHERE post_title = $search;”. If you ran this query, you’d be fried.
How about we search for a post named “abc;DROP TABLE wp_posts”. Well, the final SQL query will be: “SELECT * FROM wp_posts WHERE post_title = abc;DROP TABLE wp_posts;”. Do I really have to explain what’s wrong here? Your wp_posts table will be dropped!
An SQL injection is perhaps the most notorious web vulnerability, especially among beginners. It’s so tempting to just include the user-provided data in the query. It’s also arguably one of the most dangerous vulnerabilities out there. You mitigate it by properly sanitizing the data when creating the final query.

Source: xkcd Exploits of a Mom
Security Mindset
Before we discuss securing your WordPress themes and plugins, it’s important to start more broadly – with developing a security mindset.
The official WordPress Security Documentation proposes 3 rules:
- Don’t trust any data (even the data already stored in your database).
- Rely on the WordPress API (most core functions handle security for you).
- Keep your code up to date (new security holes get discovered all the time).
These are excellent rules to keep in mind, but I think they forgot about the most important one – make security your top priority. Let’s say you’re creating a theme or a plugin. If you’re successful, your code will run on hundreds, thousands, or even millions of websites world wide.
You are personally responsible for making sure these websites are secure. If you screw up, consequences can be astronomical. An attacker may steal personal data and scam hundreds if not thousands of people – all because of your mistake. The owner can lose their business. People can lose everything they have.
My college professor once said that a single IT professional can sink an entire company. I think it’s an understatement. They can sink lives. Think about it and do your fucking job.