Intigriti Easter XSS Challenge Write-up

Hey all, on March 13th, I was working on some boring college assignments. To take a break, I opened Twitter to see what was new and noticed that Intigriti was hosting an “Easter XSS Challenge.” I decided to give it a shot.

Here is a screenshot of the main challenge page:

The rules were clear, so there was no need to explain them.

First Try

First, I checked the page’s source code.

There was nothing interesting in the HTML code except for this script (obviously). The script.js file contained the following JavaScript code:

var hash = document.location.hash.substr(1);
if(hash){
  displayReason(hash);
}
document.getElementById("reasons").onchange = function(e){
  if(e.target.value != "")
    displayReason(e.target.value);
}
function reasonLoaded () {
    var reason = document.getElementById("reason");
    reason.innerHTML = unescape(this.responseText);
}
function displayReason(reason){
  window.location.hash = reason;
  var xhr = new XMLHttpRequest();
  xhr.addEventListener("load", reasonLoaded);
  xhr.open("GET",`./reasons/${reason}.txt`);
  xhr.send();
}

What this code does in steps:

  1. It checks if the hash property is set in the location object.
  2. It sends an XHR request to the selected value (file).
  3. It reads and prints the response to the page. Line 11 is the key to this XSS challenge. Wait, is it that easy? The page sets the innerHTML of the div to a value that has been through unescape(), so URL-encoding could be bypassed.

Let’s try it by sending a request to the 404 page. I wrote https://challenge.intigriti.io/#test and clicked enter:

Our filename was reflected in the response, so we only need to write <img src=x onerror=alert(1)> as the filename, and it should work.

As I expected, it is not that easy. The 404 page encodes the filename with percentage-encoding and removes the % character from the response. I tried to play with the 404 page and insert some random texts and characters, but to no avail. It seemed like a dead-end.

Second Round I fingerprinted the server to check if it was a server-related issue. The HTTP response header included server: Google Frontend, but that isn’t the real HTTP server. This is a Google App Engine response header. I did it manually because Nmap would give the same output: server: Google Frontend.

I sent a very long filename to trigger a 4xx error, and the result was:

So this is an Apache server. We need to trigger an error page that can respond with the URL. Hmm.

We only have control over 4xx pages, and since we can’t see the 404 page, we need a 403 error page. We can get a 403 page if we try to access .htaccess or server-status, but the content of the URL is escaped, so we will see HTML entities.

We can write our payload as percentage encoding, which is accepted and will be written as it is. We have to double encode <img src=x onerror=alert(1)> since we are sending it from another page. After encoding, it will be %253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E.

We need to be in the website root, so we use ../server-status?%253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E.

Well, it worked! But I forgot about the CSP because it was too small and I didn’t notice it:

content-security-policy: default-src 'self'

After seeing this, I dropped all the XSS payloads because we need to insert a script from inside this origin. Furthermore, the

So here is one trick to executing the <script> tag with innerHTML using an <iframe> with the srcdoc attribute, which specifies the HTML content of the page to show in the inline frame. But the biggest question is, where can we upload the script to this host? That’s impossible.

Last Round We can’t use the 403 as a script since it is not valid JS. But wait! What about the custom 404 page? It seems like legitimate JS.

404 - 'File "test" was not found in this folder.'

We have control over the test string. We need to make it valid JS. Instead of test, we must replace it with ';alert(1);'.

404 - 'File "'; // 404 - 'string' = NaN
alert(1); // pew pew pew 
'" was not found in this folder.' // string is valid JS

Great!

Now all in one:

https://challenge.intigriti.io/#..//server-status/?%253Ciframe%2520srcdoc%253D%2527%253Cscript%2520src%253D%2522x%252527%253Balert(document.domain)%253B%252527%2522%253E%253C%252Fscript%253E%2527%253E%253C%252Fiframe%253E%250A

The challenge was super fun! I had a great time (kudos to Intigriti). It took me less than an hour to solve it. I submitted my solution afterward, and it was confirmed. I hope you learned something and good luck with future challenges.