Log Injection, or Improper Output Neutralization for Logs, occurs when an application writes unsanitized user-supplied data directly to a log file. An attacker can exploit this by crafting input that contains special characters, such as newline (\n or %0a) and carriage return (\r or %0d) characters (CRLF). By injecting these characters, an attacker can forge new log entries, making the logs unreliable or actively misleading. They might inject fake log lines to cover their tracks, mislead administrators, or inject malicious content (like HTML/JavaScript) if the logs are viewed in a web-based log viewer (leading to XSS). 📜✍️➡️🤥
Log Injection undermines the integrity and trustworthiness of your application logs:
Covering Tracks: Attackers can inject fake log entries that mimic normal activity or error messages, confusing administrators and hiding their real actions (e.g., injecting a fake “Login Successful for admin” line after a real failed attempt).
Misleading Forensics: During an incident investigation, fake log entries can send investigators down the wrong path, wasting time and resources.
Log Viewer XSS: If logs are viewed in a web-based utility, injected HTML/JavaScript (<script>alert(1)</script>) can execute in the administrator’s browser, leading to session hijacking or other attacks on the admin.
Denial of Service: Injecting large amounts of junk data can fill up log storage, potentially causing DoS.
Reference Details
CWE ID:CWE-117OWASP Top 10 (2021): A09:2021 - Security Logging and Monitoring Failures
Severity: Medium
This vulnerability is independent of specific web frameworks but depends on how the logging framework or custom logging logic handles data. The core issue is trusting that user input is single-line and benign.Key Remediation Principles:
Sanitize Log Data: Before logging any user-supplied string, sanitize it by removing or encoding newline (\n), carriage return (\r), and other control characters.
Use Structured Logging: Use a logging format like JSON. The logging library will typically handle encoding the user input as a single string value within a JSON key, naturally preventing it from breaking the log entry’s structure.
Output Encoding (Log Viewers): Ensure any web-based log viewing application properly HTML-encodes the log data before rendering it in the browser to prevent XSS.
Avoid Logging Unnecessary Input: Don’t log user input verbatim unless necessary. Log identifiers or sanitized summaries instead.
Sanitize user input by removing newlines before logging: safe_username = username.replace('\n', '_').replace('\r', '_').
Use Structured Logging: Configure logging (e.g., with python-json-logger) to output JSON. The user input will be safely contained within a JSON string value.
Identify all log statements (logger.info, warn, error, etc.) that include user-controlled input (request.GET, POST, headers). Submit input containing URL-encoded newline characters (%0a, %0d). Check the raw log files (not a web viewer) to see if fake log entries were successfully injected. If logs are viewed in a web UI, also test with HTML/script payloads (<script>...).
Sanitize Input: Manually remove or encode newline characters from user input before logging it: safeUsername = username.replace("\n", "_").replace("\r", "_").
Use Logback/Log4j2 Encoders: Configure your logging framework to use an encoder (like LogstashLogbackEncoder or JsonLayout) that outputs logs as JSON objects. This ensures user input is properly escaped and contained within a JSON string value.
Use PatternLayout Escaping: In modern Log4j2/Logback PatternLayout, you can use %replace(%msg){'[\r\n]','_'} to replace newlines in the message.
Identify log statements (log.info, warn, error) that include user-controlled input (path variables, request parameters, headers). Submit input containing URL-encoded CRLF characters (%0d%0a) and fake log messages. Check raw log files for injected lines. If a web UI log viewer is used, also test XSS payloads (<script>...).
Sanitize Input: Manually strip newline characters from any user input before logging it: var safeQuery = query.Replace("\r", "").Replace("\n", ""); _logger.LogInformation("Executing search for query: {Query}", safeQuery);.
Use Structured Logging: Use libraries like Serilog configured with a JSON formatter (e.g., Serilog.Formatting.Json.JsonFormatter). When you log _logger.LogInformation("Message", variable), the JSON formatter will automatically escape newlines and other control characters within the variable’s string value, preventing log injection.
Identify log calls (_logger.LogInformation, LogWarning, etc.) that include user input. Submit payloads with URL-encoded CRLF characters (%0d%0a) and fake log messages. Check raw log files/output (console, file, Seq) for injected lines. If using JSON logging, verify the newlines are escaped (\n, \r) within the JSON string value and do not create new log entries.
Use Structured Logging (JSON): Configure Monolog (via config/logging.php in Laravel) to use a JsonFormatter. This automatically escapes newlines within the data.
Pass as Context: Pass user input as a context array, not part of the main message string. The formatter (especially JSON) is more likely to handle this safely.
Identify log calls (Log::info, error_log) that include user input. Submit CRLF sequences (%0d%0a) and fake log messages. Check raw log files (storage/logs/laravel.log, error_log path). Verify newlines are either stripped or safely escaped (e.g., as \n within a JSON string) and do not create new log entries. Test XSS payloads if logs are viewed in a web UI.
Use Structured Logging (JSON): Configure Winston/Pino to use winston.format.json(). This is the best defense, as it escapes newlines within the JSON values.
Pass as Object: Log user input as part of an object, not concatenated into the main message string.
Identify all log calls (console.log, logger.info, etc.) that include user input. Submit CRLF sequences (%0d%0a) and fake log messages. Check raw log output (console, file). Verify that newlines are either stripped or safely escaped (e.g., \n within a JSON string) and do not create new log entries.
Sanitize: Manually remove newlines before logging: safe_query = query.to_s.gsub(/[\r\n]/, '_').
Use Structured Logging (JSON): Configure Rails logging to use a JSON formatter (e.g., Lograge with Lograge::Formatters::Json). Pass user input as part of the event payload/hash, not the main message string.
Identify log calls (Rails.logger.info etc.) that interpolate user data. Submit CRLF sequences (%0d%0a) and fake log messages. Check raw log files (log/production.log etc.). Verify newlines are stripped or escaped (e.g., in JSON string). Test XSS payloads if logs are viewed in a web UI.