JSON Web Tokens (JWTs) have become the dominant session and authentication mechanism in modern web applications, APIs, and microservice architectures. A JWT is a compact, URL-safe token format that encodes claims as a JSON object and optionally signs or encrypts them. Despite their ubiquity, JWTs are frequently misconfigured, and the consequences range from authentication bypass to full account takeover. As a penetration tester, I encounter JWT vulnerabilities on nearly every engagement involving API-based authentication.
JWT Structure Explained
A JWT consists of three Base64URL-encoded parts separated by dots: header.payload.signature. The header specifies the token type and signing algorithm (e.g., {"alg": "HS256", "typ": "JWT"}). The payload contains claims -- key-value pairs such as sub (subject/user ID), iat (issued at), exp (expiration), and custom claims like role or admin. The signature is computed over the encoded header and payload using the specified algorithm and a secret or private key. Understanding this structure is the foundation for every JWT attack, because each component introduces distinct vulnerabilities.
Algorithm Confusion Attacks
Algorithm confusion is the most critical class of JWT vulnerability. It exploits the fact that the server reads the signing algorithm from the token's header rather than enforcing a fixed algorithm.
- The "none" algorithm - The JWT specification includes an unsecured token type where alg is set to "none" and the signature is empty. If the server accepts tokens with alg "none", an attacker can forge any token by simply removing the signature. Variations include "None", "NONE", and "nOnE" to bypass case-sensitive checks. Test by modifying the header to {"alg":"none"}, adjusting the payload claims as desired, and submitting the token with an empty signature segment.
- RS256 to HS256 switching - When a server uses RS256 (asymmetric RSA), it signs with a private key and verifies with the public key. If the server also accepts HS256 (symmetric HMAC), an attacker can change the algorithm to HS256 and sign the token using the server's public key as the HMAC secret. Since the public key is often publicly available (via JWKS endpoints, TLS certificates, or API documentation), this effectively allows forging any token.
Weak Secret Brute Forcing
When JWTs are signed with HMAC algorithms (HS256, HS384, HS512), the security depends entirely on the strength of the shared secret. Weak or common secrets can be cracked offline because the attacker has everything needed: the token's header, payload, signature, and the algorithm.
- Methodology - Extract a valid JWT from the application. Use hashcat (mode 16500) or John the Ripper with jwt2john.py to brute-force the secret against wordlists. The command is: hashcat -m 16500 jwt.txt wordlist.txt.
- Wordlists - Start with common passwords (rockyou.txt), then try application-specific terms, default secrets from frameworks (e.g., "secret", "changeme", "your-256-bit-secret" from jwt.io examples). The jwt-secrets GitHub repository contains commonly used JWT signing secrets.
- Impact - Once the secret is recovered, the attacker can forge tokens for any user, escalate privileges, and bypass all authentication entirely.
Key Injection Attacks
Several JWT header parameters allow the token to specify where the verification key should be fetched from, creating injection opportunities:
- jku (JWK Set URL) - The jku header points to a URL hosting the JSON Web Key Set used for verification. If the server fetches keys from the URL specified in the token without validating against an allowlist, an attacker can host their own JWKS and sign the token with their private key.
- jwk (JSON Web Key) - The jwk header embeds the verification key directly in the token. If the server uses this embedded key for verification, the attacker can generate their own key pair, sign with their private key, and embed their public key in the jwk header.
- x5u and x5c - The x5u header points to an X.509 certificate URL, and x5c embeds the certificate chain directly. Both can be exploited similarly to jku/jwk by providing attacker-controlled certificates.
kid Parameter Injection
The kid (Key ID) header parameter tells the server which key to use for verification. The server typically uses this value to look up the correct key, but the lookup mechanism itself may be vulnerable:
- Directory traversal - If the server reads key files from disk using the kid value, path traversal sequences like ../../../dev/null can redirect verification to a known or empty file. Signing with an empty string as the secret when kid points to /dev/null is a classic exploit.
- SQL injection - If the kid value is used in a database query to retrieve the signing key, SQL injection can extract the signing secret or manipulate the query to return a known value. For example: kid: "key1' UNION SELECT 'attacker-controlled-secret'--".
- Command injection - In rare cases, the kid value may be passed to a system command, enabling OS command injection through the JWT header.
Claim Manipulation
Even when the signature mechanism is secure, improper claim validation creates vulnerabilities:
- Subject (sub) modification - Changing the sub claim to another user's identifier to impersonate them. This requires the signature to be bypassed through one of the above techniques.
- Role and privilege escalation - Modifying claims like role, admin, is_admin, or permissions to escalate from a regular user to an administrator.
- Expiration manipulation - Setting exp to a far-future timestamp or removing it entirely. Check whether the server enforces expiration.
- Audience and issuer - Modifying aud or iss claims to use tokens across different services or environments that share signing keys but have different authorization levels.
Token Lifetime and Refresh Token Issues
Even properly signed JWTs can create security issues through poor lifecycle management. Access tokens with excessively long lifetimes extend the window for stolen token abuse. Refresh tokens that never expire or are not rotated on use enable persistent access after credential changes. Missing token revocation means that even after logout or password change, existing JWTs remain valid until expiration. Test by capturing tokens and verifying they become invalid after logout, password change, and account deactivation.
JWE vs JWS Considerations
Most JWT implementations use JWS (JSON Web Signature), which provides integrity but not confidentiality -- the payload is merely Base64URL-encoded and readable by anyone. If the token contains sensitive data (PII, internal IDs, privilege levels), this information is exposed to the client. JWE (JSON Web Encryption) provides confidentiality through encryption, but introduces its own attack surface including algorithm confusion on encryption algorithms and potential padding oracle attacks. Always decode the token to inspect its contents -- you may find sensitive information that should not be client-readable.
Step-by-Step Testing Methodology
- Capture and decode - Intercept JWT tokens from authentication responses, cookies, or Authorization headers. Decode at jwt.io or with jwt_tool to examine the header and payload structure.
- Test "none" algorithm - Modify the header to alg:none and remove the signature. Submit with various capitalizations.
- Test algorithm switching - If the server uses RS256, obtain the public key and test switching to HS256 with the public key as the HMAC secret.
- Brute force weak secrets - For HMAC-signed tokens, run hashcat -m 16500 against common wordlists.
- Test key injection - Modify jku, jwk, x5u, and x5c headers to point to attacker-controlled keys.
- Test kid injection - Try directory traversal, SQL injection, and command injection in the kid parameter.
- Manipulate claims - Change sub, role, admin, and exp claims and resubmit with a valid signature using any bypass discovered.
- Test token lifecycle - Verify tokens are invalidated after logout, password change, and permission changes. Test expired tokens.
Tools
- jwt_tool - The most comprehensive JWT testing tool. Supports all attacks including algorithm confusion, key injection, claim tampering, and automated scanning with the -M at flag.
- jwt.io - Browser-based JWT decoder and debugger for quick inspection and manual claim editing.
- hashcat - Mode 16500 for JWT HMAC secret cracking. GPU-accelerated for high-speed brute forcing against large wordlists.
- John the Ripper - With jwt2john.py for converting JWT tokens to a crackable format. Supports rule-based attacks for systematic secret guessing.
- Burp Suite JWT extensions - JSON Web Tokens and JWT Editor provide in-proxy token manipulation, automatic re-signing, and key confusion attack support.
Remediation
Secure JWT implementation requires multiple controls working together. Pin the signing algorithm on the server side -- never read it from the token header. Use strong secrets for HMAC: at least 256 bits of entropy, generated with a cryptographically secure random number generator. For asymmetric algorithms, use RSA keys of at least 2048 bits or ECDSA P-256. Ignore jku, jwk, x5u, x5c, and kid headers unless absolutely necessary, and validate them against strict allowlists if used. Set short access token lifetimes (5-15 minutes) and implement proper refresh token rotation. Validate all claims on every request: check exp, nbf, iss, and aud. Store signing keys securely using HSMs or key management services. Implement token revocation through short lifetimes combined with a blocklist for immediate invalidation.
Want to learn more about this topic? Read my expertise page on Web Application Security →
Comments
No comments yet. Be the first!
Leave a Comment