Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NRockhouse/683646b517b3097cb041b702eca5ba50 to your computer and use it in GitHub Desktop.
Save NRockhouse/683646b517b3097cb041b702eca5ba50 to your computer and use it in GitHub Desktop.

Hey again (Inti)griti, hope y'all are doing well. Thanks for the challenge as usual.

Vulnerability

The main vulnerability is a very limited XSS on line 41, whereby arbitrary data from the r URL inserted is appended into the DOM as such ({url} being the injection point).

If you're not being redirected, click <a href=${url}>here</a>

However, it is limited by two checks in place.

  1. Every single property in both window and document object is checked for the keyword javascript. If found, the property is deleted entirely, leaving it undefined (and possibly causing runtime errors). [line 5-11]
  2. The url variable is not allowed to contain <, >, ", ', (space), via a regex check on line 26.

Exploit with user interaction

Despite not being able to escape from the <a> tag, it still allowed attribute injection inside the <a> tag. To ensure highest possibility of the exploit triggering, we'll make use of the onmouseover event, as well as tweaking the CSS style of the element so it covers the entire page. Following is the beautified version of the styles we'll be injecting.

position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;

Next, we cannot create separate attributes in the element the "normal" way because spaces and quotes are not allowed. Despite so, HTML syntax is very lax and attributes do not necessarily need to be in quotes, and we can separate them with newlines as well, or %0a URL-encoded.

Since this time we're supposed to alert the flag instead of document.domain, the value of the alert function should be document.getElementById('flag').innerText, but we have to get around the quotation mark for the JS string flag. One of the common method is to use a chain of String.fromCharCode concatenated, but for a simple string as such, I prefer to use the regex-to-string trick /flag/.source, which I've seen posted on Twitter from some time ago.

Lastly, as this element is supposed to be shown a second later only if the initial attempt to redirect via window.location fails, we have to make it fail. This can be done by attempting to connect to an HTTPS "website" that never exists on a random IP, in this case I'll use https://1.2.3.4/.

Final payload: https://challenge-0121.intigriti.io/?r=https://1.2.3.4/%0aonmouseover=alert(document.getElementById(/flag/.source).innerText)%0astyle=position:fixed;left:0;top:0;width:100%;height:100%

which results in (cleaned up by Chrome)... <a href="https://1.2.3.4/" onmouseover="alert(document.getElementById(/flag/.source).innerText)" style="position:fixed;left:0;top:0;width:100%;height:100%">here</a>

Payload can be triggered by waiting 6 seconds and then moving the cursor in the page.

Exploit without user interaction

It is possible to achieve this with DOM clobbering combined with the fact that the domain has a wildcard DNS record (*.challenge-0121.intigriti.io leads to the same place).

The exploitable snippet in question is line 32, window.location = window.origin + "/" + url;. If the original window.origin is undefined, an element with a matching id will take its place as a global variable. We can abuse the protection mechanism which deletes any variable containing the javascript keyword and wipe out the original window.origin. Since this particular challenge subdomain supports wildcard, using the subdomain javascript.challenge-0121.intigriti.io will lead to the removal of window.origin.

Before I knew about this subdomain configuration, I researched on window.origin and found it has some interesting quirks, which would return the parent window's domain if window.origin is called in an iframed page, but only if the iframed page is about:blank. I attempted messing with this quirk for a while but failed before discovering the actual solution.

As for assigning a new value for window.origin, we can just have the previous <a> element to have an id=origin attribute. window.origin would now return the element itself, but when concatenated with other strings, it will pass through .toString() and return the href value.

window.origin now has the same value as url, so when concatenate to form the new window.location, we have to negate the repeated JavaScript URL by suffixing our payload with //, commenting out the second half.

Lastly, we can't have javascript in our payload as it'll tick off the protection mechanism as well. HTML attribute values can be encoded as HTML entities with ampersand, one of the syntax is &#00; where 00 is any ASCII character's decimal value. t in javascript is ASCII decimal 116 so I'll replace it with &#116;, or %26%23116%3B after URL encoding.

Final payload: https://javascript.challenge-0121.intigriti.io/?r=javascrip%26%23116%3B%3Aalert(flag.innerText)%2F%0Aid%3Dorigin

Wait 5 seconds, and a redirection will be attempted for javascript:alert(flag.innerText)//javascript:alert(flag.innerText)/.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment