flawopen.com/Vulnerabilities/JWT Algorithm Confusion
JWT Algorithm Confusion: Pin Key and Algorithm Policy
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.
alg header, incompatible key/algorithm handling, or an unsecured none token accepted by policyCore Concepts & Subsystem Terms
- RS256 and HS256
RS256is an RSA signature using a private/public key pair.HS256is an HMAC using the same high-entropy secret to sign and verify; these key types must not be interchangeable.- JOSE
algheader - 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
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.
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.
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.
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
| Case | Failure condition | Engineering fix |
|---|---|---|
CVE-2015-9235 · jsonwebtoken ≤4.2.1 · GitHub Advisory: Critical | Untrusted 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
# 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,
)
# 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.
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);
}
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
- 1Trust boundary: identify how the verifier obtains keys. A public key from a trusted, fixed JWKS endpoint is not the same as a key supplied by an arbitrary token header.
- 2Policy agreement: compare the configured allow-list, JWT header
alg, JWKalg, key type, and actual verification algorithm; all must match the intended policy. - 3PyJWT CVE-2026-48523: affected releases are 2.9.0–2.12.1. Check whether
jwt.decode()receives aPyJWKor aPyJWKClientresult, and whether external parties control accepted JWKS signing keys. - 4Impact check: inspect issuer, audience, expiry, token type, and authorization checks. A signature bypass alone does not prove an attacker receives administrator access.
Defensive Engineering & Prevention Rules
- ✓Upgrade affected libraries: use PyJWT 2.13.0 or later for CVE-2026-48523; update
jsonwebtokenaffected by CVE-2015-9235 (versions before 4.2.2). Check current advisories before release. - ✓Pin the algorithm in trusted configuration: pass a fixed allow-list such as
algorithms=["RS256"]; never derive it from an unverified JWT header. - ✓Bind key type and algorithm: allow one expected algorithm per key, validate JWK
kty/alg, and do not mix symmetricHS*with asymmetricRS*keys. - ✓Validate the claims and token purpose: check signature, issuer, audience, expiry, subject, and the expected JWT type before authorization. Requiring a claim only checks that it exists; configure its value checks too.
Sources
- RFC 8725 — JSON Web Token Best Current Practices, §§2.1, 3.1–3.2
- RFC 7518 — JSON Web Algorithms
- CVE-2015-9235 — jsonwebtoken verification bypass (affected <4.2.2)
- PyJWT CVE-2026-48523 — JWK algorithm allow-list bypass
- PyJWT API Reference — decode and algorithm allow-list
- CWE-347 — Improper Verification of Cryptographic Signature
- node-jsonwebtoken fix for CVE-2015-9235 — algorithm allow-list
Framework mappings
- CWE reference for the weakness described in this guide: CWE-347
- OWASP API Security Top 10 — API2:2023 Broken Authentication