$ cat writeup.md…
$ cat writeup.md…
tjctf
Task: Flask web challenge with a custom admin bot, a localhost-only admin endpoint, and a URL fetch feature. Solution: Abuse `/admin/../` path normalization to satisfy the bot regex while reaching `/?url=...`, then let the server-side request leak the appended flag to a webhook.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
i designed my own admin bot! and i included an admin page that should be super duper secure...
English summary: We are given a Flask site on port 5000, an external admin bot endpoint, and source files for index.html, admin-bot.js, and app.py. The goal is to make the bot leak its flag despite a strict /admin/ URL check and a localhost-only admin page.
The challenge works because several individually small bugs compose into one usable chain.
The bot code only accepts URLs matching:
urlRegex: /^https:\/\/chained\.tjc\.tf\/admin\//
After validating the submitted URL, it visits:
url + flag
So the bot does not put the flag in a header or cookie. It literally concatenates the flag onto the submitted URL before loading it.
The Flask app has different behavior for POST / and GET /:
POST / checks isSafe(url) with a blacklist before fetching.GET /?url=... does not apply that blacklist.That means the SSRF sink is reachable safely through a GET request if we can get the bot onto the index route.
/adminThe /admin endpoint only returns attacker-controlled JavaScript when:
request.remote_addr == '127.0.0.1'
At first glance this looks ideal for bot XSS, since the bot visits from localhost.
The main page uses a CSP with script-src 'self', so reflected payloads such as /admin?q=<script>... appear in the response but do not execute. That makes direct XSS a dead end.
/admin/../The key idea is to exploit the gap between raw-string validation and server-side path normalization.
This URL passes the bot regex because the raw string starts with /admin/:
...
$ grep --similar