JWT Misconfiguration
What Is Broken and Why
JWTs that accept alg: none, trust the algorithm declared in the token header, or use
guessable HMAC secrets allow attackers to forge arbitrary tokens — including admin-level
claims — without knowing any signing key. Algorithm confusion attacks exploit servers
that accept both RS256 (asymmetric) and HS256 (symmetric): the attacker signs with the
public key (which is public) using HS256 and the server verifies it as a valid HMAC.
Key Signals
Authorization: Bearer <base64url>.<base64url>.<base64url>in requests- JWTs stored in cookies (check
httpOnly,Secure,SameSiteflags) - JWKS endpoint at
/.well-known/jwks.jsonor/oauth/certs(exposes public key) algfield in JWT header — watch forHS256,RS256,nonekid(Key ID) field in JWT header — check for path traversal or SQLi- Short or dictionary-based HMAC secret (crack with hashcat)
Methodology
- Decode the JWT header and payload (
base64urldecode each part). - Note the
algvalue; attempt to change it tononeand remove signature. - If
algisRS256, fetch the public key from JWKS endpoint; re-sign with HS256 using the public key as the HMAC secret. - If
algisHS256, attempt to crack the secret withhashcatorjwt_tool. - Check
kidvalue — test for path traversal (../../dev/null), SQLi, or SSRF. - Modify
expclaim to far-future timestamp; attempt to use expired tokens. - Escalate claims: change
role,admin,sub,userIdin payload after forging.
Payloads & Tools
# jwt_tool — Swiss army knife for JWT attacks
pip install jwt_tool
jwt_tool TOKEN -X a # alg:none attack
jwt_tool TOKEN -X s # algorithm confusion (RS256→HS256)
jwt_tool TOKEN -C -d wordlist.txt # crack HMAC secret
# hashcat JWT cracking
hashcat -a 0 -m 16500 TOKEN wordlist.txt
# Manual alg:none
# 1. Decode header: {"alg":"RS256","typ":"JWT"}
# 2. Change to: {"alg":"none","typ":"JWT"}
# 3. Re-encode and append empty signature: header.payload.
# (trailing dot is required)
# kid SQLi
{"kid": "' UNION SELECT 'attacker_secret' --", "alg":"HS256"}
# Sign token with 'attacker_secret'
# kid path traversal (sign with empty string)
{"kid": "../../dev/null", "alg":"HS256"}
# /dev/null reads as empty → sign with empty string ""
Bypass Techniques
| Attack | Technique |
|---|---|
alg: none |
Change header alg to none, drop signature, keep trailing dot |
| Algorithm confusion | Fetch RS256 public key; use it as HS256 HMAC secret |
| Weak secret | Crack short/dictionary HMAC with hashcat -m 16500 |
kid path traversal |
Point kid to /dev/null or known empty file; sign with "" |
kid SQLi |
Inject SQL into kid to return attacker-controlled key from DB |
Missing exp |
If no expiry check, reuse old tokens indefinitely |
jku/x5u injection |
Point to attacker-hosted JWKS to supply own public key |
| Embedded JWK | Inject jwk into header containing attacker's own public key |
Exploitation Scenarios
Algorithm confusion to admin:
Setup → API uses RS256; JWKS endpoint public at /.well-known/jwks.json.
Trigger → Fetch public key → re-sign token with HS256 using public key as secret → set
"role":"admin" in payload.
Impact → Full admin access without any private key.
alg:none on misconfigured library:
Setup → Old version of JWT library doesn't reject alg: none.
Trigger → Set alg: none, modify sub to another user's ID, remove signature.
Impact → Arbitrary account takeover.
kid path traversal to RCE:
Setup → kid is used to load key from filesystem without sanitization.
Trigger → kid: "../../proc/self/fd/0" with socket input; or ../../tmp/evil.
Impact → Attacker controls signing key; full token forgery.
False Positives
alg: nonerejected with 401 — library properly validates algorithm.- RS256 server that only accepts RS256 (not HS256) — algorithm confusion not applicable.
- JWKS endpoint present but server uses pinned key in code —
jku/x5uinjection blocked.
Fix Patterns
// Always whitelist algorithm — never derive from token header
jwt.verify(token, secret, { algorithms: ['HS256'] });
// For RS256: pin the public key in code, don't trust jku/jwk headers
jwt.verify(token, publicKeyPem, { algorithms: ['RS256'] });
// Always validate exp
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
ignoreExpiration: false // default false — make it explicit
});
Related Skills
JWT attacks are a class of [[auth-bypass]] specific to token-based systems. kid SQLi
chains into [[sql-injection]]; kid path traversal chains into [[path-traversal]].
Weak session management after JWT compromise connects to [[cookie-attacks]] and
[[session-fixation]].