I made a JS API! Sadly I had no time to finish it :(

Category: web

Solver: aes, Liekedaeler, lukasrad02

Flag: GPNCTF{N0_C0MM3NT_b7c62b1e}

Writeup

We are given the source code of a Node.JS web application. Looking around, we see that the source code consists of a server.js file that runs on the server and a script.js file that is served to the client by the server.

Taking a closer look at the server code, we find four HTTP routes that are defined. Let’s take a look at them one after another!

First, there is the / GET route. This route seems pretty simple. There is no user-controlled content and it just serves the static home page containing a number of form elements. We can use this form to send POST requests to either /chal or /admin.

Next up, there is the /script.js GET route. This is a little more interesting. It serves the script.js file from the source code but performs one critical modification: If the cookie of the request contains some previously generated random bytes, a placeholder is replaced with the actual flag. These random bytes are, however, unknown to us as the attacker and randomly generated on every start of the app. To conclude, we need to somehow make a request to this endpoint with the correct cookie to get the flag.

Third, we have the /chal GET endpoint. This endpoint takes a the html parameter and uses string templating to insert it into a basic HTML scaffold. It is quite obvious that such an approach is vulnerable to XSS. The server provides this file with a content security policy. This prevents loading all resources from anywhere. There is only one exception for scripts that are allowed from the page origin itself and also inline scripts. Thus, the XSS here is not prevented by the CSP. But this so far does not lead to a vulnerabilty that discloses the flag.

So, the /admin GET route must be the culprit. The code follows the typical admin bot pattern that is present in a number of challenges: A chromium instance is remote-controlled using pupeteer. The browser instance is initialized with the random bytes as a cookie. That cookie is set with the httpOnly flag so that it cannot be retrieved using the document.cookie property via JavaScript. Thus, we need to construct an attack that makes the controlled browser visit the /script.js route with the cookie and returns the response back to us. We can provide this route with an HTML payload that is then put into the /chal route we looked at earlier. We are returned a screenshot of the displayed page at the end.

The typical XSS payloads were not effective in this case as the content security policy prevented a fetch to the JavaScript file and the cookie is HTTP only. As we are provided with a screenshot of the page in the end, a few other attack vectors open up. We played arounnd a little and came to the conclusion that a very simple solution to this challenge was to just redirect to the script that is then displayed and captured. To reproduce, you can simply use the following payload and put it into the admin input field:

<script>
  window.location.href = "/script.js"
</script>

There also are a number of other more complex attack vectors, one of which is explained in our writeup of the todo-hard challenge.