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.

Challenge Overview

Here is a screenshot of the main challenge page:

The rules were clear, so there was no need for further explanation.

First Attempt

First, I checked the page’s source code.

There was nothing particularly interesting in the HTML code except for this script. 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();
}

Code Breakdown

  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. The page sets the innerHTML of the div to a value that has been unescaped, so URL encoding could be bypassed.

Initial Exploit Attempt

I attempted to access a non-existent page by appending #test to the URL:

https://challenge.intigriti.io/#test

The filename was reflected in the response. I then attempted to use:

<img src=x onerror=alert(1)>

as the filename, expecting the XSS to trigger.

However, it wasn’t that simple. The 404 page encoded the filename using percentage encoding and removed the % character from the response. I experimented with various inputs but couldn’t bypass this limitation.

Second Round: Server Fingerprinting

I fingerprinted the server to check if it was a server-side issue. The HTTP response header included:

Server: Google Frontend

But this wasn’t the real HTTP server. It was a Google App Engine response. Using manual techniques (since Nmap would provide the same output), I triggered a long filename error to reveal more details:

This confirmed that the server was running Apache. The goal was now to trigger an error page that reflected the request URL. Since the 404 page sanitizes inputs, I focused on the 403 page, which could be triggered by accessing restricted files such as .htaccess or server-status.

By sending double-encoded payloads, I was able to reflect the input without encoding. The final payload was:

../server-status?%253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E

Bypassing CSP

The Content Security Policy (CSP) was:

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

This meant inline scripts wouldn’t execute, and external scripts were blocked unless they were hosted on the same origin.

Final Exploit

To execute a <script> tag via innerHTML, I used an <iframe> with the srcdoc attribute. This trick allows an attacker to embed an entire HTML document inside the <iframe>, bypassing CSP limitations.

Since we controlled the custom 404 page, we injected JavaScript into it:

404 - 'File "'; // 404 - 'string' = NaN
alert(1); // XSS Triggered
'" was not found in this folder.' // Valid JavaScript

The final exploit URL was:

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

Conclusion

This was an incredibly fun challenge! Kudos to Intigriti for creating it. The entire process took me less than an hour to solve. I submitted my solution, and it was confirmed.

I hope you learned something new. Best of luck with future challenges!