Information Disclosure with Jwt Tokens
How Information Disclosure Manifests in Jwt Tokens
Information disclosure in JWT tokens occurs when sensitive data is encoded in the token payload without proper protection, allowing attackers to extract valuable information through simple base64 decoding. Unlike encryption, JWT signing only provides integrity verification, not confidentiality. This means anyone can decode a JWT token and read its contents, making it critical to never include sensitive information in the payload.
A common vulnerability appears when developers include user email addresses, phone numbers, or internal identifiers directly in the JWT payload. For example:
// VULNERABLE: Exposes user email in token payload
const token = jwt.sign({
userId: user.id, // internal database ID
email: user.email, // PII exposure
role: user.role, // may reveal internal structure
issuedAt: Date.now()
}, process.env.JWT_SECRET);Attackers can extract this information by decoding the token:
# Extract payload from JWT
JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
PAYLOAD=$(echo $JWT | cut -d'.' -f2)
echo $PAYLOAD | base64 -d
# Outputs: {"userId":"123","email":"admin@example.com","role":"admin"}
Another manifestation involves excessive claims that reveal system architecture. Including database table names, internal service identifiers, or implementation details in tokens provides attackers with reconnaissance data. For instance, tokens containing "tenantId" or "departmentCode" can help map organizational structure.
Misconfigured JWT libraries can also lead to information disclosure through default claims. Some implementations automatically include "iat" (issued at), "exp" (expiration), and "nbf" (not before) timestamps, which may reveal system uptime patterns or token rotation schedules that assist timing-based attacks.
Publicly accessible endpoints that return JWT tokens in responses without proper access controls create additional disclosure risks. If an authentication endpoint returns tokens in plaintext HTTP responses or logs them in server logs, sensitive session data becomes exposed to network sniffers and log analysis tools.
Jwt Tokens-Specific Detection
Detecting information disclosure in JWT tokens requires analyzing both the token structure and the application's token generation logic. Static analysis tools can identify hardcoded secrets and sensitive data patterns in token payloads, while dynamic scanning examines runtime token behavior.
middleBrick's JWT-specific scanning examines tokens across multiple dimensions:
Payload Analysis: The scanner decodes JWT tokens and analyzes payload contents against 27 regex patterns for sensitive data including PII, API keys, and system identifiers. It flags tokens containing email addresses, phone numbers, social security numbers, credit card numbers, and internal database IDs.
Claim Auditing: middleBrick checks for excessive or unnecessary claims that reveal system architecture. It identifies tokens with internal identifiers like "tenantId", "departmentCode", or database-specific references that shouldn't be exposed to clients.
Algorithm Verification: The scanner verifies JWT algorithms are configured securely. It flags the use of "none" algorithm (which provides no integrity protection) and warns about weak signing algorithms like HS256 with insufficient key lengths.
Transport Security: middleBrick checks whether tokens are transmitted over secure channels. It identifies JWTs sent over HTTP instead of HTTPS, tokens included in URLs (vulnerable to browser history and referrer leakage), and tokens stored insecurely in client-side storage.
Expiration Analysis: The scanner evaluates token expiration policies. Tokens with excessively long lifetimes (years instead of minutes or hours) increase the window for information disclosure if tokens are compromised.
Manual detection techniques include:
# Decode and inspect JWT token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
echo $TOKEN | cut -d'.' -f2 | base64 -d
# Look for sensitive data patterns
TOKEN_PAYLOAD=$(echo $TOKEN | cut -d'.' -f2 | base64 -d)
echo $TOKEN_PAYLOAD | grep -E "(email|phone|ssn|credit|password|secret|key)"
Network traffic analysis can reveal tokens transmitted insecurely. Tools like Wireshark or browser developer tools can capture JWTs in transit, exposing whether they're properly encrypted and transmitted over HTTPS.
Jwt Tokens-Specific Remediation
Remediating JWT information disclosure requires architectural changes to token design and strict adherence to security best practices. The fundamental principle is to minimize the information included in JWT payloads.
Minimal Payload Design: Only include essential identifiers in JWT tokens. Use opaque references instead of actual data:
// SECURE: Minimal payload with opaque reference
const token = jwt.sign({
sub: user.id, // Only user ID
role: user.role, // Minimal authorization data
iat: Math.floor(Date.now() / 1000)
}, process.env.JWT_SECRET, { expiresIn: '15m' });
// Store sensitive data server-side and reference by ID
app.get('/user/profile', authenticate, (req, res) => {
const userId = req.user.sub;
db.getUserProfile(userId)
.then(profile => res.json(profile))
.catch(err => res.status(500).json({ error: 'Internal error' }));
});
Database Reference Pattern: Store sensitive user data in server-side databases and use token IDs to reference it:
// Generate token with minimal data
const token = jwt.sign({
userId: user.id,
tokenId: generateTokenId() // Opaque reference
}, process.env.JWT_SECRET);
// Store extended data server-side
const tokenData = {
userId: user.id,
email: user.email,
permissions: user.permissions,
issuedAt: new Date()
};
db.saveTokenData(tokenData.tokenId, tokenData);
Secure Transmission: Always use HTTPS for JWT transmission and avoid including tokens in URLs:
// SECURE: Use HTTPS and secure headers
app.post('/login', (req, res) => {
// Authenticate user
const token = generateSecureToken(user);
// Set secure cookie with HTTP-only flag
res.cookie('jwt', token, {
httpOnly: true,
secure: true, // Only over HTTPS
sameSite: 'strict',
maxAge: 15 * 60 * 1000 // 15 minutes
});
res.json({ success: true });
});
Algorithm and Key Management: Use strong algorithms and proper key management:
// SECURE: Strong algorithm configuration
const token = jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'RS256', // Asymmetric for better security
expiresIn: '15m', // Short lifetime
issuer: 'your-app.com',
audience: 'your-app.com'
});
// Verify with proper options
jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['RS256'],
issuer: 'your-app.com',
audience: 'your-app.com'
});
Input Validation: Validate all data before including it in tokens:
// SECURE: Validate and sanitize payload data
function createSecureToken(user) {
if (!user || !user.id || !user.role) {
throw new Error('Invalid user data for token');
}
const sanitizedUser = {
id: user.id.toString(), // Ensure consistent type
role: user.role.trim() // Remove whitespace
};
return jwt.sign({
sub: sanitizedUser.id,
role: sanitizedUser.role,
iat: Math.floor(Date.now() / 1000)
}, process.env.JWT_SECRET, { expiresIn: '15m' });
}
Logging and Monitoring: Implement proper logging to detect information disclosure attempts:
// Monitor for suspicious token patterns
app.use((req, res, next) => {
if (req.headers.authorization) {
const token = req.headers.authorization.replace('Bearer ', '');
try {
const decoded = jwt.decode(token);
if (decoded && decoded.email) {
console.warn('Token contains email address:', decoded.email);
}
} catch (err) {
// Invalid token format - log for analysis
console.debug('Malformed token detected:', token.substring(0, 20));
}
}
next();
});