Log injection

Prevent untrusted text from forging log-record boundaries

Description

Log injection occurs when untrusted text is recorded without handling it for the output format. In line-oriented logs, carriage returns (\r) and newlines (\n) can make one event look like several records. CSV, JSON, HTML and terminal output also require handling their own delimiters and control characters.

Parameterized logging such as logger.info("login user={}", user) makes message assembly convenient, but does not guarantee that CR/LF in substituted values are neutralized. Encoding depends on the logging provider, layout, collector and viewer.

Potential impact

  • Forged audit events or concealed attack traces
  • Corrupted record boundaries in alerting, search, aggregation and forensic pipelines
  • Further injection in a log viewer or misleading information for operators
  • Storage and processing exhaustion when excessively long inputs are recorded

Length limits reduce resource consumption; they do not prevent record injection by themselves.

Remediation

  1. Prefer a supported logging provider and layout that structurally encode values for the final output format. Test attack strings through the complete provider-to-collector-to-viewer path.
  2. For line-oriented text, remove every \r and \n from messages and substitution arguments, or replace them with visible representations that do not break lines. Placeholders do not replace this step.
  3. Centralize repeated handling in a reviewed helper or filter. A strict immutable allow-list can work for values drawn from a small closed set. Apply length limits after handling record separators.
  4. Exclude passwords, session identifiers and access tokens. Consistently redact or mask any necessary identifying data.

Apache Commons Text's StringEscapeUtils.escapeJava converts controls such as CR/LF to Java escape sequences and can prevent line splitting in plain-text logs. It is not a general-purpose encoder for CSV, HTML or terminal output.

Examples

Before

This code uses a placeholder but still passes newlines in untrustedUser to the logging provider.

java
import jakarta.servlet.http.HttpServletRequest;
import java.util.logging.Level;
import java.util.logging.Logger;

final class VulnerableAuditLogExample {
    private static final Logger LOGGER =
            Logger.getLogger(VulnerableAuditLogExample.class.getName());

    static void recordFailedLogin(HttpServletRequest request) {
        String untrustedUser = request.getParameter("username");
        LOGGER.log(Level.WARNING, "Failed login for {0}", untrustedUser);
    }
}

After

Remove every CR/LF before writing line-oriented text. In an application, place this handling in a central helper or output layout.

java
import jakarta.servlet.http.HttpServletRequest;
import java.util.logging.Level;
import java.util.logging.Logger;

final class SafeAuditLogExample {
    private static final Logger LOGGER =
            Logger.getLogger(SafeAuditLogExample.class.getName());

    static void recordFailedLogin(HttpServletRequest request) {
        String untrustedUser = request.getParameter("username");
        String safeUser = untrustedUser == null ? "" : untrustedUser.replaceAll("[\\r\\n]", "");
        LOGGER.log(Level.WARNING, "Failed login for {0}", safeUser);
    }
}

Removing only a CRLF pair with replaceAll("\\r\\n", ""), or removing only CR or LF, leaves standalone line separators and is insufficient.

References

Use a supported logging library instead of the end-of-life Log4j 1.x.