Solution for Intigriti’s 0121 challenge

holme
7 min readFeb 1, 2021

A new month, a new challenge. Eager for a new Intigriti challenge I was happy to finally see this tweet:

Let’s jump in and read the rules!

The solution…

- Should work on the latest version of Firefox or Chrome
- Should
alert() the following flag: {THIS_IS_THE_FLAG}.
- Should leverage a cross site scripting vulnerability on this page.
- Shouldn’t be self-XSS or related to MiTM attacks
- Should be reported at
go.intigriti.com/submit-solution

At first sight, the page doesn’t seem to have any functionality nor provides us with a cool calculator. But since I crave some Intigriti swag, let’s click the link they’ve kindly provided us with:

Interesting, instead of directly taking us to the swag shop, there’s a custom delay before the page redirects us.

Time to investigate

Let’s dig into the source code to figure out what’s going on! The page loads the script script.js (you can find the whole script at the bottom of the post)

The first lines seem to check for a GET parameter with the name r and assign its value to the property r of the window object.

The next couple of lines look a bit overwhelming, so let’s break the code down into steps:

  1. Loop through the two strings ‘document’ and ‘window’
  2. Loop through all the properties for the objects ‘document’ and ‘window’
  3. Check if the property is of type ‘string’
  4. If it is, check if it contains the substring ‘javascript’
  5. If it does, delete the property

This seems like a weird approach to try and secure the application from malicious attacks. Let’s keep this functionality in mind and move on.

These next lines override the default behavior of the anchor links on the page so that the function safeRedirect will be called with the value of the href attribute instead of directly redirecting us. This was the behavior we saw when clinking the swag shop link! Then on lines 22–24 a check is made to see if r exists and if it does, a call is made to safeRedirect with r as a parameter. This looks interesting since we already know that r is set by using the GET parameter r! Let’s try to use the GET parameter now to see if we can make a redirect to https://example.com:

https://challenge-0121.intigriti.io/?r=https://example.com

Perfect! But what does this safeRedirect function even do? Time to find out:

First, a check is made to see if the url parameter provided to the function contains any of the chars: <>"' (the last being a space). If it does, the function just pops an alert box displaying: Invalid URL.. Else, a check is performed to see if the parameter begins with the value https:// if that’s the case, window.location is set to the value of the parameter. If the parameter doesn’t begin with https://, a local redirect is attempted by setting window.location to window.origin plus whatever the value of the url parameter is. Finally, the inner HTML of the tag with the id popover is set to some markup. What’s worth noting here is that the value of the url parameter is used intwo places: inside a paragraph tag and as the value of a href attribute of an anchor tag. You might now think “lmao why don’t we just inject an XSS payload into the href tag?” Well, while we can rather easily bypass the weird filtering approach which removes occurrences of the string ‘javascript’, by simply providing a payload using encoding such as j&#x41;vascript:alert(1), we have a problem. The anchor tag is inside a paragraph tag with the style display: none… So even though we’re able to set the href tag to a working XSS payload, there’s no way for a victim to fire it¹.

Try harder

The weird filtering functionality seems interesting so let’s take a closer look at that. Which properties of document and window are even available and of type ‘string’? Let’s just quickly modify the code a bit to list them all for us. Something like this will work:

Running our code in the console list all the properties for us, which includes r as expected. The others don’t look that promising. I guess we can’t influence the origin which will probably always be: https://challenge-0121.intigriti.io, right? Looking at the policy page for the challenge hints otherwise:

Notice the little ‘*’? We should probably have checked the policy page as the first thing… Anyways, we might be in luck! Let’s try some random subdomain:

Looks like any subdomain will just point towards the challenge page. Well, what will happen if we use ‘javascript’ as a subdomain…

window.origin is now undefined!? Oh! The weird filtering stuff must have noticed that the value of the property was https://javascript.challenge-0121.intigriti.io which contains the substring ‘javascript’ which then made it delete the property! Not sure how this could help us, but it seems interesting so let’s note it down before moving on.

Try harder I said - Or just be lucky

After doing some thorough cool hacking analysis - or maybe just spraying some random payloads at the page - I got something interesting:

https://challenge-0121.intigriti.io/?r=bla%0ahihi

Wow, we just escaped the href attribute! This was totally planned by using this very nice payload I prepared through my research of the challenge *checking URL*: r=bla%0ahihi. Oh yeah, that’s the payload.² So providing a new-line char in the href tag in the template string made it possible to create our own attributes for the anchor tag, eg. the id attribute… This is happening since the no quotes are surrounding the value of the href attribute which is set to the value of the url parameter controlled by us.

Wrapping it together - DOM clobbering FTW

So now we can set arbitrary attributes for an anchor tag and have discovered how to remove the origin property. Could we maybe chain these discoveries together? Of course, we can! What would happen if we set an id attribute to the anchor tag with the value origin while we use the subdomain https://javascript.challenge-0121.intigriti.io to have the origin property deleted? The answer is magic. Or maybe just DOM clobbering… Yeah, it’s definitely just DOM clobbering. Let’s try something like https://javascript.challenge-0121.intigriti.io/?r=bla%0aid=origin :

https://javascript.challenge-0121.intigriti.io/?r=bla%0aid=origin

Hype! We just successfully ‘clobbered’ the origin property of the window object! So why is this so great you might ask? Well, remember this line of the safeRedirect function:

Now that we’ve clobbered window.origin we can use the JavaScript protocol to execute JavaScript! This works since the anchor tag is will return the value of its href attribute when it’s converted to a string:

While other elements, eg. a paragraph tag would return something like this:

Payload time

Now we’re finally ready to craft our final payload and pop the alert box!

https://javascript.challenge-0121.intigriti.io/?r=j%26%23x41;vascript:alert(flag.innerText);//%0aid=origin
  1. https://javascript.challenge-0121.intigriti.io/
    Use the ‘javascript’ subdomain to delete window.origin
  2. ?r=j%26%23x41;vascript:
    Use encoding to bypass the filter
  3. alert(flag.innerText);//
    Alert the flag
  4. %0aid=origin
    Set the id of the anchor tag to origin to clobber window.origin

Let’s try it:

Yes! After patiently waiting 5 seconds, we see that our payload fires! We’ve now successfully solved the challenge!

Thanks for reading along!

@holme_sec

  1. You may have noticed that display will eventually be set to block, but this will only happen 1 second after the 5 second delay for the redirect. This means that if our browser redirects us to the target location in less than a second, we won’t ever see the change.
  2. If you didn’t catch my thick sarcastic tone, let me be clear: I discovered this by coincidence. But that’s totally ok. That can be part of the process when doing challenges and even bug bounty hunting.

Content of script.js:

--

--