May XSS Challenge - Intigriti


May 18, 2020



Last week intigriti launched a new XSS (cross site scripting) challenge. The goal of this challenge is to find a way to execute arbitrary javascript on the challenge page: "https://challenge.intigriti.io/"

The rules of the challenge:

  • Should work on the latest version of FireFox ONLY
  • Should execute the following JS: alert(document.domain)
  • Should be executed on this page (https://challenge.intigriti.io/), on this domain
  • Should work without user interaction (no self XSS) or MiTM
  • Step 1: A small recon round

    The first thing I did was taking a look at the challenge page and page source. The page did not have any input possibilities so time to look further into the source code.

    For the source code, as this is an XSS challenge mainly the javascript parts should be interesting. There is not much javascript to see except for the "widgets.js" file used for the Twitter buttons and timeline.

    Step 2: A wrong turn investigating "widgets.js"

    Time to investigate the "widgets.js" javascript file. Opening the file showed immediatly this file was minified and probably not written for this challenge. I quickly assumed this javascript code was copied from Twitter to be embedded in websites. But to be sure time to investigate this a bit further.

    Google our friend helped here to find that this part of the page code to embed Twitter timelines can be generated here: "https://publish.twitter.com/#". My thought here was what if they generated this javascript code from Twitter but added some extra javascript code and then embedded it into the challenge page?
    The only way to find out is to generate my own Twitter timeline code and compare it with the one embedded in the intigriti challenge page. I did the comparison but both files where identical. No messing happened with this embedded widgets.js file... Damn this is a dead end.

    After I noticed this would go nowhere intigriti also posted a hint onto their Twitter account. This made it clear that indeed the "widgets.js" file was not altered for this challenge:

    Step 3: Messing with the URL input

    Nothing immediatly vulnerable in the source code it looks like at this moment. Time to try some tricks with the URL input. I tried several things by adding a . or a & at the end of the URL. Something strange I noticed was that, if I added an extra non existing path to the URL it resolved and shows the main page:

    Then something else caught my attention. Adding a "/" at the end of my non existing path made the page styling and Twitter timeline disappear. The Firefox console shows errors and warnings:

    Another look at the source code and I noticed that both the stylesheet "style.css" file and the Twitter timeline javascript "widgets.js" file had a relative path link.
    It seems once I add a non existing path into the URL the server tries to load the stylesheet and javascript files from within that non existing path I gave in the URL bar due to the fact they where set as relative links in the source code.

    Step 4: Exploiting Relative Path Overwrite (RPO) via an iframe?

    Something is clearly happening when adding a "/" to the end of our URL. The stylesheet and javascript files are no longer loaded. Time to go back to our friend Google to see if we can abuse this kind of behaviour. And yes after some searching I ended up at this blog post of portswigger:

    Detecting and exploiting path-relative stylesheet import (PRSSI) vulnerabilities

    Reading the blog post shows this behaviour where the browser loads the stylesheet as a webpage is exploitable when the page is in quirks mode. Putting the page in an iframe with the correct meta tag could force this quirks mode allowing us to inject our own CSS code.
    I gave it a try but realized this technique seems to be only working in older Internet Explorer browsers as in this challgenge the javascript should be executed in the latest version of Firefox this seems again to be a dead end..

    I was able to iframe the challenge page into a webpage that forced quirks mode but the quirks mode is not inherited by the iframe. As from this point it seems to me this kind of attack only works in older Internet Explorer browsers. Dead end again...

    The test page setup setting no DOCTYPE and forcing to emulate IE7 to get into quirks mode. The challenge page is embedded via an iframe.

    The iframed challenge page runs on my webserver and is shown in quirks mode by Firefox.

    But bad luck the iframe itself does not inherit the quirks mode so we are not able to inject our own CSS code.

    As the blog post of portswigger already mentioned this will not work in Firefox. Internet Explorer seems to be the missing piece for us but the challenge must be completed in the latest version of Firefox.

    Step 5: What can we do with the Relative Path Overwrite (RPO)?

    There is clearly something going on with the relative paths of the stylesheet and the javascript. But how can this be exploited on a modern browser?
    Time to look further in any resources I could find on internet and at the end of the portswigger blog post shown earlier some other resources are mentioned. Let's have a look at what we can find there:

    This blog from @filedescriptor is able to execute XSS from the relative path overwrite via a CSS vector: "expression(alert(document.domain))" but also here quirks mode via internet explorer seems needed.

    RPO gadgets

    One of the other references takes us to a whitepaper. Especially section "2.2. Loading another file on Safari/Firefox" seems very interesting for us. Adding "/.%2E" to the end of an URL in Firefox seems to cause Firefox to send the path to a server without resolving the last encoded dots.

    A few RPO exploitation techniques

    To show what happens I did a small test. The challenge page has an image embedded at the following location: "https://challenge.intigriti.io/may.jpg"

    When I add "/.%2E" to the end of this URL in Firefox the page does no longer load the image but goes back one level to the challenge home page and due to the RPO we discovered earlier the stylesheet and javascript are not linked correctly anymore.

    Step 6: Where to get the XSS?

    The RPO behaviour is nice but does not get me an XSS to fire at this point. Already some hours spend but still it seems I am not close to a solution. Luckily at this point another tip was shared which made things clear.
    The tip was: "Take it external" and another tip already shared said "//this is not only a javascript comment".
    OK, this made it obvious for me that "//" is just a short way to write: "http(s)://" and take it external means we have to somehow force the challenge page to connect back to a webserver we control.
    Time to spin up a local python webserver and see if we can get the challenge page to connect to our webserver.

    The tips shared by intigriti that pointed me to the good direction:

    Here the code I used for the simple python webserver. The 2 certificate files key.pem and cert.pem can be generated by using following command: "openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365" and I start the python server with sudo rights on my local computer.

    With a simple python webserver running on my computer let's redirect the challenge page to my webserver via following URL: "https://challenge.intigriti.io//localhost/"

    And it works. A GET request is received and we where forwarded to the local webserver

    At this point I thought what if I add the "/.%2E" we saw earlier to the end of the URL with my redirect, what will happen then?
    And I got lucky. The redirect still works , we get back to the challenge homepage instead of my webserver page and best of all in my server logs I see due to the RPO it tried to get the "style.css" and "widgets.js" files from my webserver :-) This can definitly be expoited!

    Step 7: Chaining things together for the final payload

    What we found until now is that following URL: "https://challenge.intigriti.io//localhost/.%2E" tries to get the "style.css" and "widgets.js" file from my controlled webserver instead of the intigriti webserver.
    Time to host my own "widgets.js" javascript file in the root directory of my webserver. The challenge page will due to the Firefox RPO take my "widgets.js" file and use it instead of the "widgets.js" hosted on the intigriti webserver.
    I setup my "widgets.js" file with a simple javascript alert and this will fire on the intigriti challenge page, or at least that is what I hope it will do :-)

    My "widgets.js" file with the alert payload hosted in the root directory of my python webserver:

    The URL we found earlier that will try to fetch the "style.css" file and hopefully the "widgets.js" file from my webserver due to the redirect and RPO:

    And it works :-) the "widgets.js" file is found on our controlled webserver and our javascript payload that we placed inside is executed at the intigriti challenge page. We managed to load our arbitrary javascript in the challenge page and pop the alert box! Sweet :-)