Type Confusion with Jwt Tokens
How Type Confusion Manifests in JWT Tokens
Type confusion in JWT tokens occurs when the application makes security decisions based on assumptions about the token's structure or content that don't hold true at runtime. This vulnerability is particularly dangerous because JWTs are designed to be self-contained, and type confusion can lead to authentication bypass or privilege escalation.
The most common Jwt Tokens-specific type confusion pattern involves the typ header field. When a JWT contains typ: "JWT", libraries typically parse it as a standard JWT. However, if an attacker modifies this to typ: "JOSE" or omits it entirely, some libraries may interpret the token differently, potentially skipping critical validation steps.
// Vulnerable code - type confusion via header manipulation
const token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiYWN0aW9uIjoiQmVhciBQYXNzIn0.signature";
const decoded = jwt.decode(token, { complete: true });
if (decoded.header.typ === "JWT") {
// Normal JWT processing
verifyStandardJWT(token);
} else {
// Different processing path - type confusion opportunity
verifyAlternativeJWT(token);
}
Another Jwt Tokens-specific type confusion scenario involves numeric versus string claims. JWT specifications allow claims to be either strings or numbers, but applications often assume a specific type:
// Type confusion via numeric/string claims
const claims = jwt.decode(token);
if (claims.userId === 123) { // Assuming numeric
// Admin logic
} else {
// Regular user logic
}
Attackers can exploit this by submitting userId as a string ("123") when the application expects a number, potentially bypassing numeric comparisons or triggering unexpected code paths.
Time-based claims (exp, nbf, iat) also present type confusion opportunities. Applications may assume these are always numbers representing timestamps, but if these fields are missing or malformed, some libraries return undefined or null, leading to logic bypasses:
// Vulnerable time-based type confusion
const claims = jwt.decode(token);
if (!claims.exp || claims.exp > Date.now()) {
// Token considered valid if exp is missing or in the future
// Missing exp field causes bypass!
return true;
}
The most sophisticated Jwt Tokens type confusion attacks involve malformed token structures. Some libraries fail to properly validate token length or structure before processing, allowing attackers to craft tokens that cause buffer overflows or unexpected parsing behavior in the underlying JWT library.
JWT-Specific Detection
Detecting type confusion in JWT tokens requires both static analysis of the token handling code and dynamic testing of the token processing logic. The key is to identify where the application makes type assumptions about JWT claims and headers.
Static analysis should focus on these Jwt Tokens-specific patterns:
# Look for header-based type branching
grep -r "typ" . | grep -E "(===|==|!==|!=)"
grep -r "header" . | grep -E "(JWT|JOSE|PS101)"
# Find numeric vs string comparisons
grep -r "===\|==" . | grep -E "(Number|string|parseInt|parseFloat)"
# Time-based claim assumptions
grep -r "(exp|nbf|iat)" . | grep -E "(Date.now|getTime|timestamp)"
Dynamic testing should include submitting malformed JWTs with various header values, missing claims, and type variations. Test tokens like:
- Tokens with
typ: "JOSE"or missingtypheader - Tokens with numeric claims where strings are expected and vice versa
- Tokens with missing critical claims (
exp,sub,aud) - Tokens with extremely large or small numeric values
middleBrick's JWT security scanner specifically tests for type confusion vulnerabilities by:
- Analyzing the application's JWT handling code for type assumptions
- Submitting JWTs with manipulated header fields to test branching logic
- Testing numeric/string claim variations to identify comparison bypasses
- Checking for proper validation of all required claims
- Scanning for vulnerable JWT libraries known to have type confusion issues
The scanner provides a detailed report showing exactly where type confusion vulnerabilities exist and what specific token manipulations could exploit them.
JWT-Specific Remediation
Remediating JWT type confusion requires strict validation of token structure and claims before any security decisions are made. The goal is to eliminate any branching logic based on token type assumptions.
First, always validate the complete JWT structure before processing:
import jwt from 'jsonwebtoken';
function validateJWT(token) {
try {
// Strict validation - no type assumptions
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256', 'RS256'],
complete: true
});
// Validate header structure
if (!decoded.header ||
typeof decoded.header.typ !== 'string' ||
decoded.header.typ !== 'JWT') {
throw new Error('Invalid JWT header');
}
// Validate payload structure
const payload = decoded.payload;
if (!payload || typeof payload !== 'object') {
throw new Error('Invalid JWT payload');
}
// Validate specific claims with type checking
validateClaims(payload);
return decoded;
} catch (error) {
// Log and handle all validation failures uniformly
console.error('JWT validation failed:', error.message);
throw new Error('Invalid token');
}
}
function validateClaims(claims) {
// Validate each claim with strict type checking
if (claims.exp !== undefined) {
if (typeof claims.exp !== 'number') {
throw new Error('Invalid exp claim type');
}
if (claims.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
}
if (claims.userId !== undefined) {
if (typeof claims.userId !== 'number' && typeof claims.userId !== 'string') {
throw new Error('Invalid userId claim type');
}
}
// Additional claim validations...
}
Second, eliminate all type-based branching logic:
// BAD - Type-based branching
if (decoded.header.typ === "JWT") {
verifyStandardJWT(token);
} else if (decoded.header.typ === "JOSE") {
verifyJOSEJWT(token);
} else {
throw new Error('Unknown type');
}
// GOOD - Single validation path
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
// Process token uniformly regardless of internal structure
Third, use strict comparison operators and validate all claim types:
// BAD - Loose comparison
if (claims.userId == 123) { // Type coercion vulnerability
// Admin access
}
// GOOD - Strict comparison with type validation
if (claims.userId === 123 || claims.userId === '123') {
// Handle both numeric and string representations
const userId = Number(claims.userId);
// Continue with numeric processing
}
Finally, implement comprehensive error handling that doesn't leak information about token structure:
try {
const decoded = validateJWT(token);
// Process token
} catch (error) {
// Uniform error response - don't reveal validation details
res.status(401).json({ error: 'Authentication failed' });
}
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |