Log Injection with Jwt Tokens
How Log Injection Manifests in Jwt Tokens
Log injection in JWT tokens occurs when untrusted token data flows directly into application logs without proper sanitization. JWT tokens often contain user-controlled claims like username, email, or custom fields that can include malicious content designed to manipulate log files.
The most common attack pattern involves embedding newline characters and log event formatting syntax into JWT claims. For example, a malicious actor might create a token with:
{
"username": "admin\necho 'Sensitive data leaked' > /var/log/attack.log\n",
"email": "[email protected]"
}
When this token is logged during authentication, the application might produce:
INFO 2024-01-15T10:30:45Z: User admin
echo 'Sensitive data leaked' > /var/log/attack.log
logged in from IP 192.168.1.100
This creates multiple log entries from a single authentication event, potentially triggering alerts, hiding malicious activity between legitimate log lines, or causing log parsers to misinterpret the data.
Another Jwt Tokens-specific manifestation involves the kid (key ID) header field. Attackers can craft tokens with kid values containing special characters:
{
"kid": "../../etc/passwd",
"alg": "HS256"
}
If the application logs the complete token header during validation and the logs are stored in a structured format that supports path traversal, this could lead to log injection attacks that exploit downstream log processing systems.
Time-based JWT claims (iat, exp, nbf) can also be exploited. An attacker might create a token with future timestamps that, when logged, appear as if they're part of the log message itself:
{
"iat": 2147483647, // Max 32-bit int
"exp": 9999999999
}
This can break log sorting algorithms or cause log aggregation tools to misinterpret the timestamp, potentially hiding malicious activity in the noise of incorrectly ordered log entries.
Jwt Tokens-Specific Detection
Detecting log injection in JWT token handling requires examining both the token validation and logging code paths. Start by identifying all locations where JWT claims are logged without sanitization.
Static analysis can catch obvious cases:
# Search for JWT claim logging patterns
grep -r "jwt\.payload\|decoded\.token" . --include="*.py" | grep "print\|log\|console"
grep -r "console\.log" . --include="*.js" | grep -E "(username|email|sub|aud)"
Dynamic testing involves creating JWT tokens with malicious payloads and observing log behavior. Use tools like jwt_tool or PyJWT to craft tokens with special characters:
import jwt
import time
key = "secret"
payload = {
"username": "admin\nERROR: Critical system failure\n",
"email": "[email protected]",
"iat": int(time.time()),
"exp": int(time.time()) + 3600
}
token = jwt.encode(payload, key, algorithm="HS256")
print(f"Malicious token: {token}")
middleBrick's black-box scanning approach specifically tests for log injection by submitting JWT tokens with crafted payloads to your API endpoints and analyzing the resulting logs through multiple channels:
- HTTP response analysis for log data reflected in error messages
- WebSocket connections if your API uses real-time logging
- API response timing to detect log processing delays
- Structured log format parsing to identify injection patterns
The scanner tests 27 regex patterns for log injection, including newline injection, format string vulnerabilities, and structured log manipulation attempts. For JWT-specific testing, middleBrick examines:
- Header field injection (
kid,alg, custom headers) - Claim value injection (username, email, roles, custom claims)
- Timestamp manipulation in
iat,exp,nbffields - Base64 URL encoding bypass attempts
Integration with middleBrick's CLI makes this testing straightforward:
middlebrick scan https://api.example.com/auth \
--test jwt-log-injection \
--output json > jwt-log-test.json
This generates a security report showing whether your JWT implementation is vulnerable to log injection, with severity ratings and specific remediation steps.
Jwt Tokens-Specific Remediation
Remediating log injection in JWT token handling requires a defense-in-depth approach that sanitizes data before it reaches log systems. The most effective strategy combines input validation, output encoding, and architectural separation.
First, implement strict claim validation before any logging occurs. Use JWT libraries that support claim validation and reject tokens with suspicious content:
// Node.js with express-jwt
const jwt = require('express-jwt');
const { checkJwtClaims } = require('./jwt-utils');
const jwtCheck = jwt({
secret: process.env.JWT_SECRET,
algorithms: ['HS256'],
isRevoked: async (req, payload, done) => {
// Validate claims before proceeding
const validation = checkJwtClaims(payload);
if (!validation.isValid) {
return done(null, true); // Revoke token
}
done(null, false);
}
});
The checkJwtClaims function should enforce strict patterns:
function checkJwtClaims(payload) {
const patterns = {
username: /^[a-zA-Z0-9_.-]{3,30}$/, // Alphanumeric, dots, underscores, hyphens
email: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$/,
role: /^(admin|user|guest)$/
};
for (const [claim, pattern] of Object.entries(patterns)) {
if (payload[claim] && !pattern.test(payload[claim])) {
return { isValid: false, reason: `Invalid ${claim} format` };
}
}
// Check for newline characters
for (const value of Object.values(payload)) {
if (typeof value === 'string' && value.includes('\n')) {
return { isValid: false, reason: 'Newline characters detected' };
}
}
return { isValid: true };
}
Second, sanitize log output using structured logging with proper escaping. Never log raw JWT claims directly:
import structlog
from html import escape
def safe_log_jwt_claims(claims):
sanitized = {}
for key, value in claims.items():
if isinstance(value, str):
# Escape newlines and special characters
sanitized[key] = value.replace('\n', '\\n').replace('\r', '\\r')
else:
sanitized[key] = value
# Use structured logging with controlled fields
structlog.get_logger().bind(
user_id=sanitized.get('sub'),
username=sanitized.get('username'),
authenticated=True
).info('JWT authentication successful')
# Example usage
def authenticate_jwt(token):
try:
claims = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
safe_log_jwt_claims(claims)
return claims
except jwt.InvalidTokenError as e:
structlog.get_logger().error('JWT validation failed', error=str(e))
raise
Third, implement log injection detection at the logging layer itself. Create custom log handlers that detect and block suspicious patterns:
package logging
import (
"regexp"
"github.com/sirupsen/logrus"
)
var logInjectionPattern = regexp.MustCompile(`(?:\r\n|\r|\n|;|`\u0000)`)
type secureFormatter struct {
jsonFormatter *logrus.JSONFormatter
}
func (f *secureFormatter) Format(entry *logrus.Entry) ([]byte, error) {
// Check for injection patterns in all fields
for _, value := range entry.Data {
if str, ok := value.(string); ok && logInjectionPattern.MatchString(str) {
// Replace suspicious content with safe placeholder
entry.Data["sanitized"] = true
break
}
}
return f.jsonFormatter.Format(entry)
}
// Usage
log := logrus.New()
log.Formatter = &secureFormatter{&logrus.JSONFormatter{}}
Finally, use middleBrick's continuous monitoring to verify your remediation. The Pro plan's scheduled scanning will repeatedly test your JWT endpoints, ensuring that log injection vulnerabilities don't reappear as your codebase evolves.