JWT alg=none and kid Injection: Classic Token Bugs That Still Appear
JWTs break in predictable ways when libraries trust the header. Two old tricks still show up on homegrown auth and misconfigured gateways: accepting alg: none, and letting kid steer key lookup into attacker-controlled material.
You do not need a crypto PhD. You need Repeater, a JWT editor, and skepticism toward “signed” cookies.
Decode before you attack
Split the token. Read claims: sub, role, admin, tenant. Note algorithm and kid. Many apps verify signature then trust role blindly — so signature bypass equals privilege bypass.
alg=none (and friends)
Strip the signature, set header "alg":"none" (and variants like None, NONE depending on the library), keep claims escalated. If the server accepts it, verification is optional in practice.
Related: algorithm confusion — RS256 public key used as HMAC secret. When you can obtain the PEM from a JWKS URL, some stacks still verify HS256 with that public key as the shared secret. PortSwigger’s JWT labs train this cleanly; reproduce on in-scope targets only when the same pattern fits.
kid injection
kid often selects a file or database row for the HMAC secret. Try:
- Path traversal:
../../dev/nullor empty secrets on Unix-like systems - SQL-ish payloads if
kidhits a query - Pointing
kidat a JWKS or URL the server fetches (SSRF + key control — rare but loud)
Success looks like forging tokens the app treats as valid for your test user with elevated claims — not brute-forcing production users.
What I put in proofs
Before/after tokens (redacted), the exact header mutation, and a privileged action that only works with the forged token. Avoid dumping other customers’ data even if the bug allows it.
Hardening notes to include
- Ignore client
alg; pin algorithms server-side. - Do not let
kidtouch the filesystem. - Use vetted libraries and explicit key sets.
- Keep privileges out of mutable claims when you can resolve them server-side from
sub.
When not to report
Expired tokens that still decode in jwt.io are not vulns. “I changed claims without resigning and got 401” means the app worked. Save the writeup for acceptance of forged material.
JWT bugs feel retro until an API gateway still honors none. Check the header like you check IDOR — every new auth surface deserves one forged token attempt.