๐ Welcome back to the blog!
Today weโre diving into the world of JWT Tampering. Youโll see how an attacker can go from a basic user to full-blown admin by exploiting insecure JWT implementations. Weโll walk through how JWTs work, common vulnerabilities, and top it off with a real-world demo using our intentionally vulnerable app โ PinewoodStore.
Ready to hack some tokens? Letโs go. ๐
๐ง What is JWT Authentication?
JWT (JSON Web Token) is a compact, self-contained way to transmit information between parties securely. Itโs often used for authentication in modern web applications and APIs.
Hereโs what happens when a user logs in:
- Server verifies credentials โ
- Server generates a JWT signed with a secret/private key ๐งพ๐
- Server sends this JWT back to the client
- Client sends the JWT in every subsequent request using the
Authorizationheader
๐งฑ Structure of a JWT
. .
Each part is Base64URL-encoded and has a specific purpose:
1. Header
Purpose: Describes the token type and signing algorithm.
Format: Base64URL-encoded JSON.
Example Decoded:
{
"alg": "HS256", // Signing algorithm (e.g., HMAC-SHA256)
"typ": "JWT" // Token type
}
Key Fields:
alg: Algorithm used for the signature (HS256,RS256,ES256, etc.)typ: Always"JWT"for standard tokenskid(optional): Key ID โ used for key rotation in systems with multiple signing keys
๐ Security Note:
- Header is not encrypted, only Base64URL-encoded.
- Never accept tokens with
alg: "none"โ that means no signature at all!
2. Payload (Claims)
Purpose: Contains user data and metadata (claims).
Format: Base64URL-encoded JSON.
Example Decoded:
{
"sub": "user123", // Subject (user ID)
"iat": 1516239022, // Issued-at timestamp
"exp": 1516242622, // Expiration timestamp
"roles": ["admin"], // Custom claim
"name": "John Doe" // Custom claim
}
๐ Standard Claims (from RFC 7519):
| Claim | Purpose | Example |
|---|---|---|
sub | Subject (user ID) | "sub": "user123" |
iat | Issued at | Unix timestamp |
exp | Expiration | Unix timestamp |
aud | Audience | "aud": ["api.example.com"] |
iss | Issuer | "iss": "auth-server" |
โจ Custom Claims:
- Used to store app-specific data, like
roles,permissions,name,email, etc. - Do not store sensitive info (e.g., passwords) โ payload is not encrypted!
3. Signature
Purpose: Verifies the tokenโs integrity and authenticity.
How Itโs Created:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey )
Key Fields/Concepts:
- Signing Algorithm: Depends on
algin the header (HS256,RS256, etc.) - Secret Key / Private Key: Used to sign the token
- Signature: Final output of the algorithm; prevents tampering
โ If the header or payload is modified, the signature becomes invalid unless the attacker has the signing key.
๐ Bearer Token Usage
JWTs are typically included in the Authorization header when calling APIs:
GET /protected HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
๐ Common JWT Vulnerabilities
Letโs break down where things go wrong โ and how attackers take over accounts like a boss. ๐ง ๐ฅ
1. ๐ซ alg: none Vulnerability
If the server doesnโt enforce signature checks, an attacker can send an unsigned token:
{
"alg": "none",
"typ": "JWT"
}
๐ฅ Exploit:
curl -H "Authorization: Bearer" http://target.com/api
โ
Fix: Always verify the signature and reject alg: none.
2. ๐ Algorithm Confusion
If the server supports both RS256 (asymmetric) and HS256 (symmetric) without validationโฆ
An attacker can forge an HS256 token using the public key as a secret.
jwt.encode({"sub": "admin"}, key=public_key, algorithm="HS256")
๐ฅ Boom! Youโre admin.
โ Fix: Lock down supported algorithms and enforce strict verification.
3. ๐ Ignored Expiration
If the exp field isnโt checked, a token can be reused forever.
{
"exp": 1600000000
}
๐งช Test:
curl -H "Authorization: Bearer" http://target.com/api
โ
Fix: Always validate exp.
4. ๐ No Signature Verification
This is what we exploited in PinewoodStore ๐
Vulnerable Code:
private MapextractAllClaims(String token) { String[] parts = token.split("\\."); String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1])); return objectMapper.readValue(payloadJson, Map.class); }
๐ฑ It just decodes the token without verifying the signature.
๐ฟ Modify the payload to:
{
"sub": "admin",
"roles": ["admin"]
}
Then Base64 encode it and send it with the original header and signature. The server doesnโt care โ youโre now admin.
๐งช Real-World Demo: PinewoodStore
Hereโs how we take over an account using JWT tampering in PinewoodStore:
๐งฉ Steps:
- Log in as a normal user โ get a valid JWT
- Decode the payload:
{ "sub": "user", "roles": ["user"] } - Modify to:
{ "sub": "admin", "roles": ["admin"] } - Base64 encode and rebuild the token
- Send request with:
Authorization: Bearer
๐ฅ Youโre now in the admin panel!
๐ JWT Testing Matrix
| Test Case | Should Be Rejected? |
|---|---|
1. alg: none | โ Yes |
| 2. Expired token | โ Yes |
| 3. Tampered payload | โ Yes |
| 4. Algorithm switch | โ Yes |
| 5. Missing signature | โ Yes |
๐งช Tools to Help You Hack JWTs
- ๐ง jwt_tool โ Modify, sign, test tokens
- ๐ Burp Suite JWT Scanner Extension
- ๐งช Postman for quick token tests
- ๐ jwt.io debugger
โ JWT Security Best Practices
- ๐ Always verify the signature
- โ Never accept
alg: none - ๐ Enforce token expiration
- โ Donโt put sensitive info in the payload
- ๐ Use strong secrets (for HS256)
- ๐งพ Prefer short-lived access tokens and refresh tokens
- โ Implement role-based access control on the server
๐ง Summary
JWTs are powerful, but power without control is dangerous.
As shown in PinewoodStore, skipping a simple signature check can lead to full account takeover.
So next time you build or audit a JWT-based app, ask:
๐ค โWhat happens if someone tampers with this token?โ
If the answer is โNothingโ, then youโre doing it right. ๐