Challenge

A site that signs your session cookie as payload.signature, where payload is base64 of {"username": ...}. The flag lives on the admin account’s page, so the goal is to be treated as admin.

The trap is that two different JSON parsers touch the same payload. The signature is computed over the username pulled out with the standard library json, while the page authorizes you with the username pulled out by ujson.

1
2
# utils.py, signing
signature = hmac.new(SECRET, json.loads(payload).get("username").encode(), hashlib.sha256).digest()
1
2
# main.py, authorization
username = ujson.loads(decoded_payload).get("username")

Approach

If the two parsers can be made to read a different username from the same bytes, the signature stays valid for a name I am allowed to sign (my own) while the page authorizes me as someone else. This is a JSON interoperability bug.

Duplicate keys are the lever. Stdlib json and ujson disagree on how to handle a duplicate username key once a lone unicode surrogate is involved. A key like username\ud888 reads as a separate key to json (so it signs over the first, real username) but collapses onto username for ujson and takes the last value.

{"username": "test", "username\ud888": "admin"}

A local testbed printing both parses confirms the split.

[ujson] Payload: {'username': 'admin'}
[json]  Payload: {'username': 'test', 'username\ud888': 'admin'}
[index] Signatures match: True
[index] Resolved username (ujson): admin

Solution

Log in normally as test to obtain a valid signature, which is HMAC(SECRET, "test"). Swap the cookie’s payload for the crafted one above. The server recomputes the signature over the json username, which is still test, so it matches, then resolves the active user with ujson, which is admin.

token = eyJ1c2VybmFtZSI6ICJ0ZXN0IiwgInVzZXJuYW1lXHVkODg4IjogImFkbWluIn0.<your test signature>

Loading the index as admin shows the admin’s page and the flag.

Flag

MetaCTF{how_did_you_find_me}