JWT Algorithm None (Static Artifact): Unsigned JWT Token Forgery for Authentication Bypass
Theory
Why This Matters
CVE-2015-9235 documented the algorithm:none vulnerability in the node-jsonwebtoken library, which at the time was the most widely deployed JWT library in the Node.js ecosystem. The vulnerability allowed any attacker in possession of a valid JWT to strip the signature, change the algorithm header to "none", and submit the unsigned token with arbitrary payload modifications. Numerous other JWT libraries — including python-jose, PyJWT before 1.5.0, and php-jwt — were subsequently found to have the same flaw. Because JWTs are used as the primary session mechanism in many modern single-page applications and REST APIs, this single class of vulnerability has appeared in CTF competitions, bug bounty programmes, and production breaches across the decade since its discovery.
Core Concept
A JSON Web Token (JWT) is a compact, URL-safe token format defined in RFC 7519. It consists of three Base64URL-encoded components separated by dots: header.payload.signature. The header is a JSON object specifying the token type and the signing algorithm (e.g., {"alg": "HS256", "typ": "JWT"}). The payload is a JSON object containing claims (e.g., {"sub": "1234567890", "role": "user"}). The signature is computed over Base64URL(header) + "." + Base64URL(payload) using the algorithm and key specified in the header.
The RFC 7518 specification defines none as a valid algorithm value, intended for use cases where the JWT integrity is guaranteed by the transport layer (e.g., TLS) rather than a cryptographic signature. A vulnerable JWT library that accepts the algorithm from the token header and dispatches signature verification based on its value will, when it sees alg: none, skip signature verification entirely — accepting any token with a matching algorithm header regardless of what the signature field contains (which should be an empty string).
The attacker's precondition is possession of any valid JWT from the target application. The attack then proceeds in three steps: decode the header, change alg to none (or a case variant), modify the payload to add privileges, re-encode, and append an empty signature field (the trailing dot with nothing after it). The critical implementation error is that the library uses the attacker-controlled algorithm field to decide how to verify the token, rather than using a server-configured expected algorithm.
Case variants matter because some implementations normalise the algorithm string before comparison while others do not: none, None, NONE, nOnE, NoNe. Vulnerable implementations that do a case-sensitive string comparison against "HS256" will not match none (correct) but may fail to check the none case at all.
The empty signature field is a critical verification detail. A correct alg:none token looks like header.payload. — note the trailing dot with nothing after it. Some libraries require this exact format; others may accept header.payload (no trailing dot) or header.payload.anythinghere. Testing all variants is necessary.
Technical Deep-Dive
Manual construction of an alg:none bypass:
# Step 1: Decode an existing valid JWT
JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJyb2xlIjoidXNlciJ9.sig"
# Decode header and payload
python3 -c "
import base64, json
parts = '$JWT'.split('.')
def decode(s):
s += '==' # re-add padding
return json.loads(base64.urlsafe_b64decode(s))
print('Header:', decode(parts[0]))
print('Payload:', decode(parts[1]))
"
# Step 2: Forge header with alg:none, elevated payload
python3 -c "
import base64, json
def b64url(d):
return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b'=').decode()
header = b64url({'alg': 'none', 'typ': 'JWT'})
payload = b64url({'user': 'alice', 'role': 'admin'})
print(f'{header}.{payload}.') # trailing dot = empty signature
"
Using jwt_tool for automated testing:
pip install jwt_tool
# Test alg:none and case variants automatically
jwt_tool "$JWT" -X a
# -X a runs the algorithm:none attack and all case variants
# Output shows which variants (none/None/NONE/nOnE) were accepted
Variants to test manually in Burp Repeater:
none
None
NONE
nOnE
NoNe
nOne
For each variant, test both:
header.payload. (trailing dot, empty signature)
header.payload (no trailing dot)
header.payload.invalid (non-empty invalid signature)
Security Assessment Methodology
- Identify JWT usage — Look for the three-segment dot-separated pattern in Cookie headers,
Authorization: Bearerheaders, and JSON response bodies. Decode the header of any candidate token to confirm"typ": "JWT". - Record the current algorithm — Note the
algfield from the decoded header. HS256 is the most common symmetric variant; RS256, ES256, and PS256 are asymmetric variants. - Run jwt_tool -X a — This performs the alg:none attack automatically across all case variants. Examine the output for any response that differs from the baseline (different status code, body content, or session behaviour).
- Test manually in Burp Repeater — For each accepted variant, submit a token with an escalated payload (change
roletoadminorsubto an admin user's ID). Confirm that the application grants elevated access, not just that it returns a non-error HTTP status. - Test the empty-signature variants — Ensure you test both the trailing-dot format and the no-trailing-dot format, as some parsers behave differently.
- Document the full proof chain — The report must include: the original valid JWT, the forged JWT with empty signature, the HTTP request using the forged token, and the HTTP response demonstrating elevated access.
Defensive Countermeasure — Configure the JWT verification library with an explicit allowlist of permitted algorithms (e.g., only
HS256or onlyRS256) and reject any token whose header specifies an algorithm not in that list before attempting signature verification — never allow the token itself to dictate which algorithm is used for its own verification.
Common Assessment Errors
- Testing only
nonein lowercase — Many vulnerable implementations do case-insensitive checks for their supported algorithms but fail to blockNoneorNONE. Test all case variants. - Forgetting the trailing dot — A JWT with alg:none and no trailing dot (
header.payload) is a different format than one with a trailing dot (header.payload.). Test both; some parsers require the dot-terminated format. - Stopping at a 200 response — The server accepting the token without error is not sufficient proof of exploitability. Confirm that the session established by the alg:none token actually has the claimed elevated privileges.
- Not testing RS256/ES256 tokens — The alg:none attack is not limited to HS256 tokens. Any JWT-using endpoint is potentially vulnerable regardless of the original algorithm.
- Missing the confusion attack path — If alg:none fails, immediately pivot to the RS256→HS256 key confusion attack (Card 11). These two attacks are commonly tested together.
- Not checking the library version — Identifying the JWT library version (from error messages, headers, or package files) allows direct CVE lookup. A known-vulnerable library version is a finding even before exploitation is confirmed.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0007 | Knowledge of authentication, authorisation, and access control methods | Explains the JWT structure and the signature verification model that the alg:none attack subverts |
| K0065 | Knowledge of policy-based and role-based access controls | Demonstrates how arbitrary payload modification via alg:none directly overrides role-based access controls |
| S0001 | Skill in conducting vulnerability scans and recognising vulnerabilities in security systems | Develops jwt_tool usage and manual JWT manipulation skills for algorithm confusion testing |
| T0028 | Conduct and/or support authorised penetration testing on enterprise network assets | Provides a structured JWT algorithm:none assessment methodology with full proof-chain documentation guidance |
Further Reading
- RFC 7519: JSON Web Token (JWT) — IETF (Jones, Bradley, Sakimura)
- Critical vulnerabilities in JSON Web Token libraries — Tim McLean (Auth0 Security Blog, 2015, documents CVE-2015-9235)
- PortSwigger Web Security Academy: JWT Attacks — PortSwigger Research
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.