I made a JS API! Sadly I had no time to finish it :(
But I had time to make it harder!
Category: web
Solver: aes, rgw, lukasrad02
Flag: GPNCTF{TH4T_W45_D3F1N1T3LY_N0T_4N_0V3RS1GHT}
Writeup
This challenge is extremely similar to the todo
challenge in this CTF.
To be exact, the two challenges are only different by two lines, precisely the following in the server.js
file:
// NEW: run JS to replace the flag with "nope"
await page.evaluate((flag) => { document.body.outerHTML = document.body.outerHTML.replace(flag, "nope") }, flag)
We are working based on the knowledge gained from the todo challenge, so have a look at that writeup here.
What does this change to the original challenge result in? First off, our approach of redirecting to the script.js
file does not work anymore as the implicit HTML document of the displayed file is edited to remove the flag before displaying. Thus, we need a different approach.
We know that the content security policy allows including the script in the HTML payload in the /chal
endpoint. The issue is, however, that the flag is only included in a comment in the JavaScript file and is thus not reachable by a function call or similar action.
But let’s take a look at the details of the JavaScript file:
class FlagAPI {
constructor() {
throw new Error("Not implemented yet!")
}
static valueOf() {
return new FlagAPI()
}
static toString() {
return "<FlagAPI>"
}
// TODO: Make sure that this is secure before deploying
// getFlag() {
// return "GPNCTF{FAKE_FLAG_ADMINBOT_WILL_REPLACE_ME}"
// }
}
What was suspicious to us, even when we worked on the initial challenge, was the fact that the comment was included in a class. In addition, there are a number of typical JavaScript object prototype functions that are overridden, such as the toString
function.
We played around a bit and noticed that in general, the toString
function returns the source code of the class. This is due to the fact that classes are based on functions in JavaScript and the function prototype’s toString
function returns the source code of the respective function1. Thus, we simply need to call the proper toString
function on a FlagAPI
object to retrieve the flag.
In JavaScript, functions are first-class objects. Thus, we can simply overwrite a function of an object. Thus, we simply can take the function from a “clean” JavaScript class and implant it into the a FlagAPI
object as follows to retrieve the source code.
<body>
</body>
<script>
class NewClass {}
FlagAPI.toString = NewClass.toString;
document.body.append(FlagAPI.toString().replaceAll("{", "("))
</script>
As you can see, we then perform a small string replacement to prevent the printed flag from being detected by the replace
call that is executed before taking the screenshot.
In the end, we are printed the source code including the flag in the screenshot returned by the browser.