CSP Path bypass

Before I move into the solution, I would like to mention that it is possible to bypass the Content-Security-Policy when including a raw file from pastebin.com. We can find a tweet about that on Michał’s Twitter wall.

By following the instructions, I created a paste with alert(1) as a body which when accessed through https://pastebin.com/how-can-i-escape-this%2f..%2fraw/LiE18yqs should work as described in the tweet. This bypasses the CSP rule if applied correctly.

DOM Clobbering — straight-forward solution

Just to recap, the goal is to achieve a DOM XSS by defining certain variables in the window context, which have been introduced in the ‘DOM Clobbering — introduction’ section.

I searched for HTML Elements that contain protocol , host and version attributes and found out that <area> and <a> define the first two and <html> the latter. However, neither the <html> element nor the <a path=xxx protocol=xxx> can be successfully injected into the resulting iframe due to a rigorous code sanitization (via DOMPurify).

First bypass: the protocol and host

A helpful fact is that the browser automatically defines the protocol and host attributes on anchor elements. Hence, to define them we only need to create an anchor tag <a> with the href attribute pointing to the desired pastebin.com domain. Given that, the inserted element may look like <a href="https://pastebin.com" id="testPath"> which returns https:// as the protocol and pastebin.com as the host.

Nested objects in a form: fail

I didn’t find a way to successfully inject <html> elements into the resulting iframe. Neither did I find other elements having the version attribute. The reason we would like to define this variable is that we need to traverse back from the /xss/1/modules/${CONFIG.version}/${moduleName}.js path to the root / and create a URL that will download the prepared alert(1) script using the technique described in the ‘CSP Path bypass’ section.

However, it is possible to define nested objects via <form> for example. By injecting the code <form id="CONFIG"><input name="test"><input name="version"> we can access the <input> elements through CONFIG.version and CONFIG.test references. While the latter reference will evaluate to true , the former will not return what we need. It would result in crafting the following URL: https://pastebin.com/xss/1/modules/[object HTMLInputElement]/h1-magic.js which is not the URL required to download the prepared script from.

Second bypass: custom attributes

After looking for elements that return something different from [object ...] when being accessed through a reference, I discovered that <a> elements return href instead. However, attempting to insert <form id="CONFIG"><a name="version"> into DOM results in CONFIG.version returning undefined because of the anchor <a> not being a valid node for a <form> element.

Third bypass: the collection

I was stuck on defining a custom CONFIG.version for a while, but then I noticed that there is another way to perform the DOM Clobbering. We can insert two elements with the same id which creates a reference to a HTMLCollection object instead of a simple HTMLElement . Moreover, defining name attributes altogether allow accessing these elements through HTMLCollection.<name> on chromium-based browsers.

Inserting the code <a id="CONFIG" name="test"><a id="CONFIG" href="https://example.org" name="version"> into DOM results in CONFIG yielding HTMLCollection as shown on the image.

With that, casting CONFIG.version to string returns https://example.org , and casting CONFIG.test to boolean returns true.

The Complete Solution

The last missing piece is to traverse back to the /how-can-I-escape-this path and append the bypassing CSP suffix %2f..%2fraw/LiE18yqs? . An obvious attempt was to insert an anchor <a href="https://../../../../how-can-escape-this%2f..%2fraw/LiE18yqs?" id="CONFIG" name="version"> into DOM but doing so made CONFIG.version return https://../how-can-escape-this%2f..%2fraw/LiE18yqs? with only one pair of ../ , which is not enough to traverse back and download the script.

To bypass this, I used a cid: protocol (one of the least schemas allowed by DOMPurify) which returns an unmodified URL. By bringing all the pieces together, I crafted the following payload.

Straight-forward solution (Chromium-based only)

After inserting the above code into the destinated iframe on the challenge’s website, the browser requested a script with the https://pastebin.com/how-can-i-escape-this%2f..%2fraw/LiE18yqs?/h1-magic.js URL (which is a folded version of https://pastebin.com/xss/1/modules/cid:/../../../../how-can-i-escape-this%2f..%2fraw/LiE18yqs?/h1-magic.js ) and then popped out an alert which was the goal of the challenge. That means the challenge was successfully solved and I have been added into the challenge Hall of Fame! :)