FlawOpen

flawopen.com/Vulnerabilities/JWT Algorithm Confusion

JWT Algorithm Confusion: Pin Key and Algorithm Policy

Impact depends on trust model CWE-347 Cryptography & Authentication
ELI5 — The Badge Cannot Set Its Own Rules

Imagine a venue with a list of approved badge checks. A guest presents a badge that names the check to use, and a flawed guard lets that guest choose the rulebook. The safe design keeps the accepted check in trusted configuration and verifies that the key is meant for that algorithm. JWT algorithm-confusion flaws happen when a verifier lets token-controlled metadata or mismatched key policy change how a signature is checked. Forgery and privilege escalation are possible only when the particular verifier accepts the mismatch and the application trusts the resulting claims.

Target:JWT verifiers whose algorithm and key policy is derived from untrusted token metadata or mismatched key records
Vector:The untrusted JOSE alg header, incompatible key/algorithm handling, or an unsecured none token accepted by policy
Impact:Signature-validation bypass may let an attacker supply claims the application trusts; privileges depend on authorization logic and prerequisites
Remediation:Pin allowed algorithms in trusted configuration, bind each key to one algorithm, validate key metadata, and upgrade affected libraries

Core Concepts & Subsystem Terms

RS256 and HS256
RS256 is an RSA signature using a private/public key pair. HS256 is an HMAC using the same high-entropy secret to sign and verify; these key types must not be interchangeable.
JOSE alg header
An untrusted token header that names the algorithm used by the issuer. It is a value to check against verifier policy, never the policy source itself.
PyJWK
PyJWT 2.9.0–2.12.1 is affected by CVE-2026-48523 when decoding with a PyJWK; fixed in 2.13.0. The key-bound algorithm could differ from the allow-listed header value.
CWE-347
Improper Verification of Cryptographic Signature. The classification covers failures to correctly validate a signature; each affected library and configuration has its own prerequisites.

JWT Algorithm Confusion: Attack Conditions

Case 1

Unsecured-token acceptance

Some vulnerable implementations accepted a token with alg: none without a signature. RFC 8725 says unsecured JWTs should be consumed only when explicitly requested and protected by other means.

Case 2

RSA/HMAC key confusion

Historically, some verifiers let a token switch from RS256 to HS256 and treated public RSA key bytes as an HMAC secret. This requires a library/configuration that permits the mismatch.

Case 3

PyJWT CVE-2026-48523: JWK policy bypass

In affected PyJWT versions, a token can advertise an allow-listed algorithm such as RS512 while an accepted PyJWK is bound to RS256. Verification uses the key-bound algorithm. The attacker must control the private key for a registered JWK/JWKS; the advisory says this documented flow needs no unsafe flag.

Impact

Application consumes trusted claims

If signature verification accepts the mismatch, application code may trust the claims. Unauthorized access depends on issuer/key trust, claim checks, and authorization logic; admin access is not automatic.

The Mechanism & Root Cause

RFC 8725 requires applications to select allowed algorithms and bind each key to exactly one algorithm. CVE-2015-9235 affected jsonwebtoken before 4.2.2: weak algorithm validation enabled a historical RSA/HMAC verification bypass. PyJWT CVE-2026-48523 (GHSA-jq35-7prp-9v3f) affects 2.9.0–2.12.1 when jwt.decode() receives a PyJWK; the header is checked against the caller’s allow-list, but signature verification uses the JWK-bound algorithm. The advisory’s example requires an attacker-controlled private key already registered in a trusted JWKS. Upgrade to 2.13.0. JWT claims are not encrypted by signing.

JWT algorithm confusion: affected paths and fixes

CaseFailure conditionEngineering fix
CVE-2015-9235 · jsonwebtoken ≤4.2.1 · GitHub Advisory: CriticalUntrusted token alg could select HMAC verification while the service supplied an RSA public key.Use a maintained version and explicitly allow the algorithm intended for that public key.
CVE-2026-48523 · PyJWT 2.9.0–2.12.1 · GitHub Advisory: Moderate (CVSS 5.4)jwt.decode() receives a PyJWK; header algorithm passes the allow-list but verification uses the JWK-bound algorithm. Exploitation requires control of a private key accepted by the JWKS.Upgrade to 2.13.0+; bind the JWK algorithm to policy and pass a fixed allow-list.

Python / PyJWT: CVE-2026-48523

jwt_verify.py (Vulnerable PyJWT JWK use)Vulnerable
# VULNERABLE: PyJWT 2.9.0–2.12.1; JWK binds RS256 while policy allows RS512.
import jwt

def verify_token(token, jwks_client, audience):
    # A trusted JWKS lookup returns a PyJWK bound to the JWK algorithm metadata.
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    # Advisory mismatch: header says RS512, PyJWK verification uses its bound RS256 algorithm.
    return jwt.decode(
        token,
        signing_key,
        algorithms=["RS512"],
        audience=audience,
    )
jwt_verify.py (Pinned key policy)Hardened
# FIX: upgrade to PyJWT 2.13.0 or later; require one pinned key algorithm.
import jwt
from jwt import InvalidTokenError

EXPECTED_ALGORITHM = "RS512"
ISSUER = "https://issuer.example"
AUDIENCE = "api.example"

def verify_token_safe(token, jwks_client):
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    if signing_key.algorithm_name != EXPECTED_ALGORITHM:
        raise InvalidTokenError("unexpected signing-key algorithm")

    return jwt.decode(
        token,
        signing_key.key,
        algorithms=[EXPECTED_ALGORITHM],
        issuer=ISSUER,
        audience=AUDIENCE,
        options={"require": ["exp", "iat", "sub"]},
    )

Node.js: CVE-2015-9235

In jsonwebtoken 4.2.1 and earlier, verification could use the algorithm selected by the token even when the application expected an asymmetric key. The upstream patch added an algorithm allow-list with key-type-specific defaults. Use an explicit allow-list with a public key; the example shows verification only and does not construct a forged token.

verify.js (jsonwebtoken ≤4.2.1)Vulnerable
const jwt = require("jsonwebtoken");

function verifyLegacy(token, publicKey) {
    // VULNERABLE: jsonwebtoken <=4.2.1 did not bind alg policy to the public key.
    return jwt.verify(token, publicKey);
}
verify.js (Pinned RS256 policy)Hardened
const jwt = require("jsonwebtoken");

const ISSUER = "https://issuer.example";
const AUDIENCE = "api.example";

function verifyToken(token, publicKey) {
    // FIX: use a maintained release and pin one algorithm for this public key.
    return jwt.verify(token, publicKey, {
        algorithms: ["RS256"],
        issuer: ISSUER,
        audience: AUDIENCE,
    });
}

Attack Preconditions & Validation Sequence

Defensive Engineering & Prevention Rules

Sources

Framework mappings

Explore related authentication security guides and incident analyses:Complete Security Directory →