Description
The JWT None Algorithm Attack exploits a design ambiguity in early JWT library implementations. The JSON Web Token specification (RFC 7519) defines "alg": "none" as a valid algorithm value indicating an "unsecured JWS" — a token with no signature. Libraries that fail to explicitly reject this algorithm value will skip signature verification entirely when they encounter it, treating the token as implicitly valid regardless of its payload contents.
CWE-347 — Improper Verification of Cryptographic Signature directly describes this failure. An attacker who obtains any valid JWT — even their own legitimately-issued token — can modify the payload to claim any identity or privilege level and re-encode it with the none algorithm. If the server accepts it, the attacker has complete authentication bypass.
This vulnerability class emerged widely in 2015 when it was discovered that many popular JWT libraries were affected. Under A02:2021 — Cryptographic Failures, OWASP highlights improper signature validation as one of the most damaging cryptographic failures because it completely nullifies the security guarantee that the token system is designed to provide.
How It Works
A legitimate JWT issued to a regular user looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJ1c2VyIn0.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decoding reveals:
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user_123", "role": "user"}
The attacker:
- Decodes the header and payload from Base64URL.
- Modifies the payload to escalate privileges:
{"sub": "admin_001", "role": "admin"}. - Changes the algorithm to
none:{"alg": "none", "typ": "JWT"}. - Re-encodes header and payload in Base64URL, appends an empty signature.
import base64, json
header = base64.urlsafe_b64encode(json.dumps({"alg":"none","typ":"JWT"}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({"sub":"admin_001","role":"admin"}).encode()).rstrip(b'=')
forged_token = f"{header.decode()}.{payload.decode()}."
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbl8wMDEiLCJyb2xlIjoiYWRtaW4ifQ.
The forged token is submitted in the Authorization: Bearer header. A vulnerable library calls its verification function, sees alg: none, and returns the payload as valid without checking any signature.
A related variant is the algorithm confusion attack: changing alg from RS256 (asymmetric) to HS256 (symmetric) and signing the token with the server's public key, which is often publicly available. The server — if it naively uses the specified algorithm — verifies the HMAC signature using what it treats as the HMAC secret but which is actually the public key.
Impact
- Complete authentication bypass — an attacker can forge a valid token for any user in the system, including administrators, without knowing any secret.
- Horizontal privilege escalation — tokens can be forged to impersonate any specific user, enabling account takeover without credential compromise.
- Vertical privilege escalation — role and permission claims in the payload can be freely modified to grant administrative access.
- Audit trail poisoning — forged tokens carrying another user's
subclaim create misleading audit records attributing the attacker's actions to legitimate users. - Full application compromise — administrative token forgery typically provides access to user management, data exports, configuration, and all application functions.
Detection
- Decode your own JWT and attempt
alg: noneforgery — using a tool likejwt_tool(python3 jwt_tool.py <token> -X a), forge a token with a modified payload and thenonealgorithm, then submit it to a protected endpoint. - Test algorithm confusion — attempt
RS256toHS256confusion usingjwt_toolwith the-X kflag and the server's public key as the HMAC secret. - Inspect JWT library version — identify the JWT library in use from dependency manifests and check its version against known CVEs for
alg: noneacceptance. - Verify token rejection without signature — submit a token with a valid header and payload but an empty or truncated signature. A secure implementation must reject it with 401.
- Test with
nonecase variants — submit tokens with"alg": "None","alg": "NONE", and"alg": "nOnE"to test for case-insensitive acceptance.
Remediation
Explicitly specify the allowed algorithm. Never let the JWT header determine which algorithm to use for verification. Hardcode the expected algorithm in the verification call:
# Vulnerable — trusts the alg header
jwt.decode(token, secret)
# Secure — forces HS256 regardless of header
jwt.decode(token, secret, algorithms=["HS256"])
Reject none algorithm at the library configuration level. Most modern JWT libraries allow configuring an algorithm allowlist. Any algorithm not on the allowlist must result in token rejection.
Upgrade to a maintained JWT library. Ensure the library version in use has been patched for alg: none and algorithm confusion attacks. For Python: PyJWT >= 2.0; Node.js: jsonwebtoken >= 9.0; Java: java-jwt >= 4.0.
Validate all security-relevant claims. Beyond algorithm, verify iss, aud, exp, and nbf claims explicitly. A cryptographically valid token with an expired exp or wrong aud must be rejected.
