JSON Web Tokens (JWT)
TL;DR: Signed/encrypted tokens carrying claims. Bug surface: algorithm confusion, weak keys, kid injection, jku, jwk.
What it is
A JWT is base64url(header).base64url(payload).base64url(signature) where the header announces the signing/encryption alg and the payload carries claims (sub, exp, aud, …). Verification depends on the server trusting the header to pick the right key and algorithm — a long-standing source of bugs.
Preconditions / where it applies
- Service authenticates by JWT (cookie,
Authorization: Bearer, query param) - Verification path reads the header alg / kid / jku / jwk before deciding the key — instead of pinning both
- HS256/RS256 confusion possible because libraries accept either
Technique
alg: none— strip signature, set header alg tonone/None/nOnE. Old jjwt/jsonwebtoken accept it.1
{"alg":"none","typ":"JWT"}.{"sub":"admin","exp":...}.- HS256/RS256 confusion — server holds an RSA public key; attacker signs HS256 using the public key as the HMAC secret. Verifier loads “the key” and calls HMAC-verify because header said HS256.
- Weak HS256 secret — crack with
hashcat -m 16500 token.jwt wordlist. Anything<~10 random chars / dictionary will fall. kidinjection — header{"kid":"../../../dev/null"}makes the server load/dev/nullas the key (empty); sign with empty key. Or SQLi/path traversal inkidto choose attacker-controlled bytes as the key.jku/jwk— header points at a JWKS URL or embeds a public key. Some libraries fetch and trust it. Host attacker JWKS athttps://target.tld.attacker.tld/.well-known/jwks.json, sign with the matching private key.x5u/x5c— same idea with X.509 cert URLs / inline certs.- JWE direct key —
alg: dirwithenc: A256GCMand CEK from a guessable header field. - Audience / issuer confusion — token issued for service A accepted by service B (no
audcheck). Cross-tenant pivot. - None of these? Probe
exp/nbf— some libs ignore claims they don’t recognise, or compare as strings. - Tools:
jwt_tool.py, BurpJWT Editor,mkjwk.
Detection and defence
- Pin algorithm at the verifier; refuse
alg: none; refuse HS* if the key is meant to be asymmetric (compare key type to header alg). - Resolve key by trusted, server-controlled lookup — never by
jku/jwk/x5ufrom the token. - Use ≥256-bit HMAC secrets; rotate; store in KMS.
- Validate
iss,aud,exp,nbf,iatand reject unknown headers. - Where you only need session lookup, prefer opaque tokens.
- Log unusual headers (
kidcontaining path chars, unknownalg, mismatchediss). - Related: oauth-flows, saml-attacks, sso-attacks, parser-differential-saml-ruby.
References
- PortSwigger — JWT — labs covering alg confusion, jwk/jku, kid
- Auth0 — JWT handbook — claim semantics
- jwt_tool — recon and exploit framework