What is Log Injection?
Log injection occurs when user-supplied input is written to application logs without sanitizing control characters, particularly newlines (\n, \r). An attacker can use this to forge legitimate-looking log entries, erase evidence of an attack, inject malicious content into log files, or — when logs are processed by downstream systems — trigger further attacks.
Beyond obscuring an incident, log injection can be particularly dangerous when logs are fed into SIEM tools, ELK Stack, or log parsers that interpret entries as commands, run scripts based on log content, or display logs in a web interface vulnerable to XSS.
The Log4Shell vulnerability (CVE-2021-44228) demonstrated the extreme end of this class: log entries that triggered JNDI lookups, resulting in RCE.
How exploitation works
An application logs failed login attempts:
logger.warn("Failed login for user: " + username);
An attacker submits alice\nINFO: Login succeeded for user: alice as the username. The resulting log file contains:
WARN Failed login for user: alice
INFO Login succeeded for user: alice ← Forged entry
A security analyst reviewing the log sees a successful login and may not investigate further, even though no authentication occurred.
Vulnerable code examples
Java / Logback
// VULNERABLE: Unsanitized user input written directly to log
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
if (!authService.authenticate(req.getUsername(), req.getPassword())) {
log.warn("Failed login attempt for username: {}", req.getUsername()); // Tainted
return ResponseEntity.status(401).build();
}
// ...
}
Python / logging
# VULNERABLE: User input with newlines reaches the log
import logging
logger = logging.getLogger(__name__)
def process_request(user_agent):
logger.info(f"Request from: {user_agent}") # Attacker controls User-Agent header
Secure code examples
Java — sanitize before logging
// SECURE: Strip newlines and control characters from logged user input
private static String sanitizeForLog(String input) {
if (input == null) return "(null)";
return input.replaceAll("[\r\n\t]", "_")
.replaceAll("[\\p{Cntrl}]", "?")
.substring(0, Math.min(input.length(), 200)); // Limit length
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
String safeUsername = sanitizeForLog(req.getUsername());
if (!authService.authenticate(req.getUsername(), req.getPassword())) {
log.warn("Failed login attempt for username: {}", safeUsername);
return ResponseEntity.status(401).build();
}
// ...
}
C# / Serilog — structured logging
// SECURE: Serilog structured logging — user input is stored as a property value,
// not concatenated into the message template. Avoids injection in structured log sinks.
Log.Warning("Failed login attempt for {Username}", username);
// Output: {"@mt": "Failed login attempt for {Username}", "Username": "alice\nhacker"}
// The newline is escaped in JSON output — injection is contained.
What Offensive360 detects
- Unsanitized user input in log calls —
logger.info(),log.warn(),Console.WriteLine()receiving tainted strings without newline stripping - HTTP header values in logs — User-Agent, Referer, X-Forwarded-For, and other attacker-controlled headers logged without encoding
- JNDI lookup patterns in log data — Detection of
${jndi:patterns or Log4j lookup syntax in logged values (Log4Shell-style) - String concatenation in log messages — Direct string concatenation (not structured logging) that embeds user input
Remediation guidance
-
Strip or encode newlines — Replace
\r,\n, and other control characters in any user input before writing to logs. -
Use structured logging — Log libraries like Serilog, Winston, and Logback (with structured output) store values as data fields rather than interpolating them into message strings, limiting injection scope.
-
Limit logged value lengths — Truncate user-supplied values to a reasonable maximum length to prevent log flooding.
-
Validate inputs early — Reject inputs containing control characters at the API boundary before they reach logging or processing code.
-
Disable dangerous log framework features — Disable Log4j lookups (
log4j2.formatMsgNoLookups=true) and similar features that evaluate log content as expressions.