Solving the Intigriti 5K XSS Challenge

#security #xss #challenge

Intigriti recently held their second DOM-based XSS challenge, commemorating their milestone of 5k Twitter followers. The winner is randomly selected out of all valid entries. Unfortunately for me, it wasn’t me. Congrats to bincup!

The challenge was set up with a single solution in mind, but someone quickly discovered a second, unintended solution. I started hacking on this one a bit late, so I was aware of this due to the second hint they published.

While solving this, I was set on discovering this unintended solution but apparently managed to find a third (also unintended) one due to a misconfiguration of their server. I decided to publish the report I submitted to them here.

Unlike the other solutions, this one works without disabling HTTPS and is fully self-contained (requires no external resources set up by the attacker).


Description

The endpoint https://challenge.intigriti.io/2/ is vulnerable to DOM-based XSS due to unsanitized usage of URL fragment.

Proof of concept

https://challenge.intigriti.io/2/;/#Zm9vIj48aW1nIHNyYz0iLyIgb25lcnJvcj0iYWxlcnQoZG9jdW1lbnQuZG9tYWluKTsiPjxociBpZD0iZm9v

Analysis

The affected endpoint contains a script which interprets the content of the URL fragment as a relative URL which it loads as a Blob using XHR. Initially, the fragment is set to aW50aWdyaXRpLWNoYWxsZW5nZQ==, which resolves the request to the URL https://challenge.intigriti.io/2/aW50aWdyaXRpLWNoYWxsZW5nZQ==. This resource contains the image with the challenge instructions.

Upon successful completion of the XHR, the contents of the response is inserted into an img tag using a data: URL using the following template:

The instantiated version of this template is then written into the document.

Crucially, the image is wrapped in an anchor tag with the href set to the fragment itself and alt set to the the base64-decoded version of the fragment. The original fragment decodes to the string intigriti-challenge.

Clearly we can modify the fragment to an arbitrary valid base64 string, leading to insertion of arbitrary HTML and/or JavaScript code since the call to atob(b64img) is not properly escaped.

However, one hurdle remains: the place where the fragment gets inserted into the DOM is inside the handler for the XHR, which checks that the request finished with status 200 OK. Therefore, the fragment, when interpreted as a URL relative to the base URL, must point to a valid resource on challenge.intigriti.io (due to the Same-Origin Policy).

(Un)fortunately, the server mishandles semicolons: it simply ignores them and everything following them in the URL. Therefore, requesting https://challenge.intigriti.io/2/;/foo#arbitrary still returns the original challenge, i.e. the endpoint https://challenge.intigriti.io/2, with the fragment arbitrary.

However, the endpoint of the later XHR is specified via a relative URL. To canonicalize it into an absolute form, the browser needs to strip the last component of the base URL before appending the given relative URL to arrive at the final form. In this case, the last path component is foo, so the final absolute form of the XHR URL becomes https://challenge.intigriti.io/2/;/arbitrary.

This is exactly what we need—we can now set an arbitrary fragment (and therefore assign an arbitrary value to b64img) while still having the URL resolve to the challenge.

Minding the point of insertion, we construct a simple payload that executes our XSS while still leaving behind well-formed HTML:

foo"><img src="/" onerror="alert(document.domain);"><hr id="foo

Encoding the payload as base64, it becomes

Zm9vIj48aW1nIHNyYz0iLyIgb25lcnJvcj0iYWxlcnQoZG9jdW1lbnQuZG9tYWluKTsiPjxociBpZD0iZm9v

Which we insert into the challenge URL to get the POC

https://challenge.intigriti.io/2/;/#Zm9vIj48aW1nIHNyYz0iLyIgb25lcnJvcj0iYWxlcnQoZG9jdW1lbnQuZG9tYWluKTsiPjxociBpZD0iZm9v

A possible explanation of this incorrect behaviour might be the obsolete RFC 2396 which specifies that each URI path segment may contain semicolon-delimited parameters:

schema://authority/path;param1;param2?p1=q1&p2=q2
                       ^^^^^^^^^^^^^^

Remediation

If inserting user content into the page, it must be properly sanitized and encoded. The server should also be configured not to ignore the semicolon and everything following it in the URL.

Consequences

Execution of arbitrary JavaScript in the context of the page if a user follows a link with a suitable payload in the fragment. The attacker would be able to steal the user’s cookies or other secrets for this page, hijacking his session. He could also redress the UI to further deceive the user.