flawopen.com/Vulnerabilities/JWT Algorithm Confusion

JWT Algorithm Confusion: The 'none' Attack & Key Mismatch

Critical Auth Flaw CWE-347 Cryptography & Auth
ELI5 — The Self-Signed VIP Badge

Imagine a nightclub where VIP wristbands are signed in glowing invisible ink. The bouncer uses a special blacklight to check the signature. But an attacker brings a wristband with a label saying: 'Verification method: None needed.' The bouncer reads the label, shrugs, turns off the blacklight, and lets the attacker walk straight into the VIP lounge. In JWT algorithm confusion, the server lets the client dictate which cryptographic formula to use, allowing attackers to forge valid admin tokens without knowing the secret key.

Target: JSON Web Token (JWT) verification libraries
Vector: Header manipulation: {"alg": "none"} or {"alg": "HS256"} with RSA public key
Impact: Complete authentication bypass, arbitrary role elevation to superadmin
Remediation: Hardcoding expected verification algorithms, rejecting 'none', separating key types

The Mechanism & Root Cause

JWTs contain a header specifying the signing algorithm (e.g. alg: RS256). Flawed libraries trust this header value blindly. In the 'none' attack, the server accepts unsigned tokens. In the HMAC/RSA confusion attack, an attacker changes the algorithm from asymmetric RS256 to symmetric HS256 and signs the token using the server's publicly available RSA public key as the HMAC shared secret.

auth_middleware.py (Vulnerable)Vulnerable
# VULNERABLE: Letting the incoming token decide which algorithm to use
import jwt

def verify_token(token_str):
    # Attacker crafts header: {"alg": "none"}
    # Library decodes without checking if 'none' is permitted!
    payload = jwt.decode(token_str, options={"verify_signature": False})
    return payload
auth_middleware.py (Hardened)Hardened
# HARDENED: Pin the allowed algorithms and explicitly enforce signature checks
import jwt

PUBLIC_KEY = open("jwt_public.pem").read()

def verify_token_safe(token_str):
    # Strictly enforce RS256: any token with 'none' or 'HS256' is immediately rejected
    payload = jwt.decode(
        token_str,
        PUBLIC_KEY,
        algorithms=["RS256"], # Explicitly pin expected algorithm
        options={
            "verify_signature": True,
            "require": ["exp", "iat", "sub"]
        }
    )
    return payload

The Attack & Exploit Sequence

Defensive Engineering & Prevention Rules

Explore related security topics and post-mortems: Complete Security Directory →