JSON Web Tokens (JWTs) have become the backbone of modern API authentication. Stateless, compact, and self-contained, they power everything from mobile apps to microservice architectures. But their flexibility is also their Achilles' heel. Misconfigured JWT validation is one of the most common and devastating vulnerabilities in modern web applications, frequently leading to full account takeover and privilege escalation.
This comprehensive guide walks you through the anatomy of JWTs, the most critical vulnerabilities, and the exact methodology for jwt token vulnerability exploitation. Whether you are a bug bounty hunter, a penetration tester, or a developer looking to secure your own APIs, this guide provides the practical, hands-on knowledge you need to find and exploit (or fix) JWT flaws in real-world applications.
- JWT Anatomy: Header, Payload & Signature
- Attack 1: The "None" Algorithm Bypass
- Attack 2: Algorithm Confusion (RS256 to HS256)
- Attack 3: Weak Secret Key Brute Force
- Attack 4: JKU / JWK Header Injection
- Attack 5: KID (Key ID) Header Injection
- Essential JWT Exploitation Tools
- Step-by-Step Exploitation Methodology
- Remediation & Secure Implementation
- The JWT Security Checklist
- Frequently Asked Questions
Before exploiting JWTs, you must understand their structure. A JWT consists of three Base64URL-encoded parts separated by dots (.):
The JWT specification (RFC 7519) allows for an algorithm called none, which indicates that the token is unsigned. This exists for scenarios where the token's integrity is already verified by other means (like HTTPS). However, many JWT libraries blindly trust the alg field in the header, allowing attackers to strip the signature entirely.
This is one of the most devastating JWT attacks. It exploits a fundamental misunderstanding of asymmetric vs. symmetric algorithms in vulnerable JWT libraries.
- RS256 (Asymmetric): The server signs tokens with a private key and verifies them with a public key.
- HS256 (Symmetric): The server uses the same secret key for both signing and verification.
- The Flaw: If the server expects RS256 but the attacker changes the header to HS256, the library will use the public key as the HMAC secret. Since the public key is often publicly available (e.g., at /.well-known/jwks.json), the attacker can sign a forged token using the public key as the HMAC secret.
If the server uses HS256 with a weak secret (e.g., secret, password123, companyname), the secret can be cracked offline using tools like Hashcat. Once cracked, the attacker can forge any token they want.
The JWT header can include a jku (JWK Set URL) or jwk (JSON Web Key) parameter that tells the server where to fetch the public key for verification. If the server does not validate this URL against an allowlist, an attacker can point it to their own server and sign tokens with their own keys.
The kid (Key ID) header parameter specifies which key from a set should be used for verification. If the application uses the kid value to look up a key in a database or file system without proper sanitization, attackers can inject SQL, path traversal, or command injection payloads.
| Tool | Purpose | Link |
|---|---|---|
| jwt_tool | The Swiss Army knife for JWT attacks. Supports all attacks covered in this guide. | github.com/ticarpi/jwt_tool |
| JSON Web Tokens (Burp Extension) | Decode, modify, and resign JWTs directly within Burp Suite. | BApp Store |
| Hashcat | Crack weak JWT secrets using GPU-accelerated brute force (mode 16500). | hashcat.net |
| jwt.io | Online decoder and debugger. Useful for quick inspection (never paste sensitive tokens). | jwt.io |
| jwt-cracker | Lightweight CLI tool for brute-forcing HS256 secrets. | github.com/brendan-rius/c-jwt-cracker |
| KeyHunter | Automated discovery of exposed JWKS endpoints and public keys. | Various GitHub repos |
When you encounter a JWT in a penetration test or bug bounty, follow this systematic approach:
- Decode & Inspect: Decode the header and payload. Note the algorithm, claims, and any sensitive data.
- Test "None" Algorithm: Change the header to alg: none, remove the signature, and resend. Try case variations.
- Test Algorithm Confusion: Look for exposed JWKS endpoints. If RS256 is used, try switching to HS256 and signing with the public key.
- Brute Force the Secret: Extract the signature hash and run it through Hashcat with a strong wordlist.
- Test JKU/JWK Injection: Modify the jku header to point to your controlled server with your own keys.
- Test KID Injection: Try SQL injection, path traversal, and command injection in the kid header.
- Test Expiration Bypass: Modify the exp claim to a far-future timestamp.
- Test Role Escalation: Modify claims like role, isAdmin, or scope in the payload.
Fixing JWT vulnerabilities requires a defense-in-depth approach. Here is how to implement JWTs securely.
- Hardcode the Algorithm: Never trust the alg field from the token header. Configure your library to accept only the expected algorithm (e.g., RS256).
- Use Strong Secrets: For HS256, use a cryptographically random secret of at least 256 bits (32 bytes). Store it in a secrets manager, never in code.
- Prefer Asymmetric Algorithms: Use RS256 or ES256 instead of HS256. This eliminates the algorithm confusion attack entirely.
- Validate JKU/JWK URLs: If using jku, strictly validate the URL against an allowlist of trusted domains.
- Sanitize KID Input: Treat the kid header as untrusted user input. Use parameterized queries and strict path validation.
- Enforce Expiration: Always validate the exp claim. Use short-lived access tokens (5-15 minutes) with secure refresh token rotation.
- Never Store Sensitive Data: The payload is only encoded, not encrypted. Never put passwords, PII, or session secrets in the JWT.
- Use Established Libraries: Use well-maintained, audited libraries (e.g., jose for Node.js, PyJWT for Python, jjwt for Java). Avoid custom JWT implementations.
- ✓Reject the "none" algorithm: Configure your JWT library to explicitly reject tokens with alg: none.
- ✓Hardcode expected algorithm: Never trust the alg header. Enforce RS256 or ES256 in the decoder configuration.
- ✓Use strong, random secrets: For HS256, use at least 256 bits of cryptographically random data. Rotate secrets periodically.
- ✓Validate expiration claims: Always verify the exp claim. Use short-lived tokens (5-15 minutes).
- ✓Sanitize JKU/JWK URLs: Validate against a strict allowlist of trusted domains.
- ✓Sanitize KID header: Use parameterized queries and strict path validation for kid lookups.
- ✓Never store sensitive data in payload: Treat the JWT payload as public. No PII, passwords, or secrets.
- ✓Implement token revocation: Use a short-lived access token + refresh token pattern with server-side revocation lists.
- ✓Use established libraries: Avoid custom JWT implementations. Use audited, well-maintained libraries.
- ✓Implement token binding: Bind tokens to client characteristics (IP, User-Agent) to prevent token theft and replay.
- ✓Monitor for JWT anomalies: Log and alert on failed JWT validations, unusual algorithms, and expired token usage attempts.
The most common JWT vulnerability is the "None" Algorithm Bypass, where the attacker changes the header's alg field to none and removes the signature. Vulnerable libraries accept the token as valid without verifying any signature, allowing the attacker to forge arbitrary claims (like admin role). The second most common is weak secret brute force, where developers use guessable secrets like "secret" or "password123".
Yes. The header and payload of a JWT are only Base64URL-encoded, NOT encrypted. Anyone who intercepts the token can decode and read all its contents. This is why you must never store sensitive information (passwords, PII, session secrets) in a JWT. Only use JWTs for non-sensitive claims like user ID, role, and expiration time.
HS256 is a symmetric algorithm: the same secret key is used for both signing and verification. It is faster but requires secure distribution of the secret to all verifying parties. RS256 is an asymmetric algorithm: a private key signs the token, and a public key verifies it. The public key can be freely distributed. RS256 is more secure and eliminates algorithm confusion attacks, which is why it is recommended for production use.
JWTs are bearer tokens -- whoever holds them has access. To prevent theft: (1) Always transmit JWTs over HTTPS. (2) Store them in httpOnly, Secure, SameSite=Strict cookies, NOT in localStorage (which is vulnerable to XSS). (3) Use short expiration times (5-15 minutes) with secure refresh token rotation. (4) Implement token binding to tie the JWT to client characteristics like IP or device fingerprint.
Neither is inherently more secure -- it depends on implementation. JWTs offer scalability benefits (stateless, no server-side storage) but introduce unique risks (no built-in revocation, algorithm confusion attacks). Session-based authentication is simpler and supports instant revocation but requires server-side state. For most applications, a hybrid approach works best: short-lived JWTs for API access combined with server-side refresh token revocation.
Start by capturing a valid JWT in Burp Suite. Decode it using jwt.io or jwt_tool to inspect the header and payload. Then systematically test: (1) "none" algorithm bypass, (2) algorithm confusion (RS256 to HS256), (3) weak secret brute force with Hashcat, (4) JKU/JWK header injection, (5) KID header injection (SQLi, path traversal), (6) expiration bypass, and (7) role/privilege escalation by modifying payload claims. Always document each test with clear proof-of-concept steps.