You logged into https://app.cyberlab.example as bob, a regular user. The site issued you the JWT below. Reading other people's email is locked behind role: admin, which the server checks by reading the role claim out of your token.
- Decode the token. Read its claims.
- Change your role to admin (and/or change the alg to
none). Re-assemble the token. - Send the forged token at the “Admin Panel” endpoint and see what the vulnerable server does.
- Switch the server to Hardened mode. Try the same forged token. Read the rejection reason.
alg to "none")role to "admin")app.cyberlab.example
VULNERABLE
If you're stuck — the canonical attack
- Click Set alg: none. The header changes from
{"alg":"HS256",...}to{"alg":"none",...}. - Click Set role: admin. The payload's
roleclaim changes from"user"to"admin". - Click Send to /admin/users. Watch the vulnerable server's log — it accepts the token because it honored the
algin the header (which now says “none”, meaning no signature check). You see other users' emails. - Switch to Hardened. Click Send again. The hardened server pins
alg: HS256, rejects anything else, and rejects HS256 tokens whose HMAC doesn't validate against its secret key.
The vulnerable server made one design choice: it called a library function that auto-detected the algorithm from the token header. That single choice eliminated the entire signature check. The fix was equally small: pin the expected algorithm at the verifier and reject anything else.
The wider lesson: trust nothing the token says about how to verify itself. The token can claim any algorithm; your verifier must enforce the one you actually expect, and validate every claim (iss, aud, exp) against your own expectations — not against what the token requests.
A real attacker doesn't get the convenient editor above. They use curl and a Base64 tool. The math is identical.