Skip to content

CWE-93: CRLF Injection - Java

Overview

CRLF (Carriage Return Line Feed) Injection occurs when attackers inject \r\n characters to manipulate HTTP headers, log files, or other line-based formats.

Primary Defence: Strip or reject newline characters (\r, \n, \r\n) from all user input before using in HTTP headers or logs, use Spring Framework's ResponseEntity and HttpHeaders which provide built-in validation, validate header values against strict allowlists or regex patterns, use structured logging frameworks (SLF4J with JSON formatters) to prevent log injection, and implement proper Content-Type headers to prevent CRLF injection and HTTP response splitting attacks.

Common Vulnerable Patterns

Direct Header Manipulation

// VULNERABLE - CRLF in headers
@GetMapping("/download")
public ResponseEntity<byte[]> download(@RequestParam String filename) {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Disposition", "attachment; filename=" + filename);
    return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
}

// Attack: filename = "test.pdf\r\nContent-Type: text/html\r\n\r\n<script>alert('xss')</script>"

Unvalidated Redirects

// VULNERABLE
@GetMapping("/redirect")
public String redirect(@RequestParam String url) {
    return "redirect:" + url;  // Can contain CRLF
}

// Attack: url = "/page\r\nSet-Cookie: session=evil"

Log Injection

// VULNERABLE - Log forgery
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

public void logUserAction(String username, String action) {
    logger.info("User {} performed {}", username, action);
}

// Attack: username = "admin\r\nINFO: User hacker performed GRANT ADMIN"

Secure Patterns

Safe Header Manipulation

// SECURE - Use Spring's ContentDisposition
import org.springframework.http.*;

@GetMapping("/download")
public ResponseEntity<byte[]> download(@RequestParam String filename) {
    // Remove CRLF and use Spring's builder
    String safeName = filename.replaceAll("[\\r\\n]", "");

    HttpHeaders headers = new HttpHeaders();
    headers.setContentDisposition(
        ContentDisposition.attachment()
            .filename(safeName)
            .build()
    );

    return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
}

Why this works:

This pattern prevents CRLF injection by combining explicit character removal with Spring Framework's type-safe header construction API. The regex [\\r\\n] matches and removes all carriage return and line feed characters from the filename, eliminating the raw characters attackers could use to inject additional headers. This sanitization happens before the filename is used in any header construction, ensuring that even if subsequent code has vulnerabilities, the input has already been cleaned.

Spring's ContentDisposition builder provides RFC 2183-compliant header construction that automatically handles special characters safely. When you use .filename(safeName), the builder properly quotes and encodes the filename value according to HTTP header specifications. If the filename contains spaces, quotes, semicolons, or international characters, the builder applies the appropriate encoding (such as RFC 2231 extended parameter encoding for non-ASCII characters) to ensure the value doesn't break header parsing while preserving the original filename's intent.

Using headers.setContentDisposition() with the builder pattern is significantly safer than string concatenation like headers.add("Content-Disposition", "attachment; filename=" + filename). The builder ensures proper formatting of the entire Content-Disposition header, including correct semicolon placement, quote handling, and encoding of special characters. This approach is framework-integrated, meaning it receives security updates and handles edge cases that manual string manipulation would miss. Always prefer framework-provided header construction methods over manual string building when dealing with user-controlled values in HTTP headers.

URL Encoding for Redirects

// SECURE - Validate and encode
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@GetMapping("/redirect")
public String redirect(@RequestParam String returnUrl) {
    // Validate it's a local URL
    if (!returnUrl.startsWith("/") || returnUrl.startsWith("//")) {
        return "redirect:/";
    }

    // Remove CRLF
    returnUrl = returnUrl.replaceAll("[\\r\\n]", "");

    return "redirect:" + returnUrl;
}

// SECURE - Use UriComponentsBuilder
import org.springframework.web.util.UriComponentsBuilder;

@GetMapping("/search")
public String search(@RequestParam String query) {
    String url = UriComponentsBuilder
        .fromPath("/results")
        .queryParam("q", query)  // Automatically encoded
        .build()
        .toUriString();

    return "redirect:" + url;
}

Why this works:

These redirect patterns demonstrate layered security against both CRLF injection and open redirect vulnerabilities. The first pattern validates that the redirect URL is local to the application by checking that it starts with a single / (a relative path) and doesn't start with // (which browsers interpret as a protocol-relative URL that could point to an external domain). This validation prevents attackers from redirecting users to phishing sites while also limiting the scope where CRLF injection could be exploited. Only after validation does the code remove CRLF characters, providing defense-in-depth.

The explicit removal of \r and \n using regex prevents HTTP response splitting even in frameworks or configurations that might not automatically sanitize redirect URLs. While Spring Framework typically provides some protection, explicitly removing these characters ensures consistent security across different versions and deployment environments. Attackers often use CRLF injection in redirects to inject Set-Cookie headers, cache control directives, or even inject content after a double CRLF sequence, so this sanitization is critical for the Location header specifically.

The second pattern demonstrates using UriComponentsBuilder, which is the preferred approach for constructing URLs with dynamic parameters in Spring applications. When you call .queryParam("q", query), Spring automatically percent-encodes the query value according to RFC 3986, converting CRLF characters to %0D%0A and other special characters to their percent-encoded equivalents. This encoding prevents the characters from being interpreted as header delimiters while maintaining a valid URL structure. The builder pattern is safer than manual string concatenation because it handles edge cases like multiple parameter values, special characters in parameter names, and proper delimiter placement. Always use UriComponentsBuilder when constructing redirect URLs with user input rather than string interpolation.

Safe Logging (Parameterized)

// SECURE - Use parameterized logging (SLF4J)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

public void logUserAction(String username, String action) {
    // SLF4J automatically sanitizes parameters
    logger.info("User {} performed {}", username, action);
    // CRLF in username won't create fake log entries
}

Why this works:

SLF4J's parameterized logging provides automatic protection against log injection by treating parameters as data rather than part of the log message format. When you use placeholder syntax like "User {} performed {}" with separate arguments, SLF4J handles the interpolation safely. The framework renders each parameter in a way that preserves the log entry structure - even if username contains \r\n, it won't create a new line in the log file but will instead be escaped or encoded depending on the underlying logging implementation (Logback, Log4j2, etc.).

This protection works because SLF4J doesn't perform simple string concatenation. Instead, it uses a formatting mechanism that clearly distinguishes between the message template and the parameter values. When the log statement is processed, each parameter is converted to a string and inserted at the corresponding {} placeholder, but special characters in the parameters are handled by the logging framework's encoder. Most production logging configurations use patterns that escape newlines or encode them as \n, preventing them from creating new log lines.

The key advantage over string concatenation (logger.info("User " + username + " performed " + action)) is that parameterized logging makes log injection attacks impossible at the framework level. You don't need to remember to sanitize inputs before logging - the framework handles it automatically. Additionally, parameterized logging is more performant (the message is only formatted if the log level is enabled) and enables better log analysis because log aggregation tools can parse the structured format. While you should still validate and sanitize input at application boundaries, parameterized logging provides defense-in-depth specifically for logging operations.

Manual Sanitization for Logs

// SECURE - Explicit CRLF removal
import org.owasp.esapi.StringUtilities;

private String sanitizeForLog(String input) {
    if (input == null) return null;

    // Remove CRLF and control characters
    return StringUtilities.stripControls(input);
}

public void logUserAction(String username, String action) {
    logger.info("User {} performed {}", 
        sanitizeForLog(username), 
        sanitizeForLog(action));
}

// OR use Apache Commons
import org.apache.commons.lang3.StringUtils;

private String sanitizeForLog(String input) {
    if (input == null) return null;
    return StringUtils.normalizeSpace(input);  // Replaces \r\n with space
}

Why this works:

Explicit log sanitization provides an additional security layer for environments where you want complete control over how special characters are handled in logs. The OWASP ESAPI StringUtilities.stripControls() method removes all control characters (ASCII 0-31 and 127), which includes not only CRLF but also tab characters, escape sequences, and other characters that could be used to manipulate log output or terminal displays. This comprehensive approach prevents both log injection and terminal escape sequence attacks that could affect administrators viewing logs.

The Apache Commons StringUtils.normalizeSpace() alternative takes a different approach by replacing all whitespace sequences (including \r, \n, and multiple spaces) with a single space character. This maintains readability while preventing log injection - if an attacker provides "admin\r\nINFO: Fake entry", it becomes "admin INFO: Fake entry" on a single log line, making the attack obvious and ineffective. This approach preserves the content of the user input while removing its ability to create structural changes in the log file.

Using a dedicated sanitizeForLog() method creates a centralized point for log sanitization that can be consistently applied across your application. This encapsulation makes it easy to update sanitization logic globally if new attack vectors are discovered. The pattern of checking for null before processing prevents NullPointerExceptions and makes the sanitization function safe to use in any context. While parameterized logging provides automatic protection, explicit sanitization is valuable when you need specific control over how input is transformed, when integrating with legacy logging code, or when you want to ensure compatibility across different logging frameworks and configurations.

// SECURE - Use ResponseCookie builder
import org.springframework.http.ResponseCookie;

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletResponse response) {
    String sessionId = generateSessionId();

    // Validate no CRLF in session ID
    if (sessionId.matches(".*[\\r\\n].*")) {
        throw new IllegalStateException("Invalid session ID");
    }

    ResponseCookie cookie = ResponseCookie.from("SESSIONID", sessionId)
        .httpOnly(true)
        .secure(true)
        .path("/")
        .maxAge(Duration.ofHours(1))
        .sameSite("Strict")
        .build();

    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    return ResponseEntity.ok().build();
}

Why this works:

This cookie pattern prevents CRLF injection through pre-validation combined with Spring's type-safe cookie construction API. The regex check sessionId.matches(".*[\\r\\n].*") detects any CRLF characters in the session ID before the cookie is created. By throwing an IllegalStateException when CRLF is detected, the code fails securely rather than attempting to sanitize the input - if a session ID somehow contains CRLF, it indicates a serious problem in the session generation logic that should be investigated, not quietly fixed.

Spring's ResponseCookie builder creates Set-Cookie headers that conform to RFC 6265, automatically handling the proper formatting of cookie attributes. Each method call (.httpOnly(true), .secure(true), etc.) adds the corresponding attribute with correct syntax. The httpOnly flag prevents JavaScript access to the cookie, mitigating XSS-based session hijacking. The secure flag ensures the cookie is only transmitted over HTTPS, preventing interception. The sameSite("Strict") setting prevents the cookie from being sent in cross-site requests, providing CSRF protection.

Using ResponseCookie.from() instead of manually constructing the Set-Cookie header string is critical for security. Manual construction like response.addHeader("Set-Cookie", "SESSIONID=" + sessionId + "; HttpOnly; Secure") is vulnerable to injection if the session ID contains semicolons or CRLF characters that could break the cookie structure or inject additional cookies. The builder pattern ensures that the cookie name and value are properly encoded, and that all attributes are formatted correctly with the right delimiters. This approach provides defense-in-depth: validation ensures no CRLF enters the system, and the framework API ensures proper encoding even if validation is somehow bypassed. Always combine input validation with framework-provided cookie APIs for maximum security.

Alternative Encoding Libraries

For URL encoding, logging contexts, and other scenarios beyond HTTP headers:

Java URLEncoder (for URLs):

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

String safe = URLEncoder.encode(userInput, StandardCharsets.UTF_8);

Why this works: These encoding libraries provide layered defense against CRLF injection across different contexts. URLEncoder.encode() percent-encodes CRLF characters to %0D%0A, making them safe for URLs and preventing header injection in Location headers. Spring's UriUtils offers specialized methods for different URI components (path segments, query parameters), ensuring proper encoding for each part of a URL structure. Apache Commons StringUtils.normalizeSpace() replaces all whitespace including CRLF with single spaces, neutralizing log injection while preserving content. OWASP ESAPI stripControls() removes all control characters (including CRLF), providing the most aggressive sanitization. Choose based on context: use URL encoding for redirects, normalization for logs, and control stripping when content preservation is less critical than security.

Spring UriUtils (comprehensive URI encoding):

import org.springframework.web.util.UriUtils;

String safe = UriUtils.encode(userInput, StandardCharsets.UTF_8);
String safePathSegment = UriUtils.encodePathSegment(userInput, StandardCharsets.UTF_8);
String safeQueryParam = UriUtils.encodeQueryParam(userInput, StandardCharsets.UTF_8);

Apache Commons (for log sanitization):

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;

// Replace CRLF with single space
String safe = StringUtils.normalizeSpace(userInput);

// Escape for Java strings
String safeJava = StringEscapeUtils.escapeJava(userInput);

OWASP ESAPI (for log sanitization):

import org.owasp.esapi.StringUtilities;

String safe = StringUtilities.stripControls(userInput);

OWASP Encoder (for URLs):

import org.owasp.encoder.Encode;

String safeUri = Encode.forUri(userInput);
String safeUriComponent = Encode.forUriComponent(userInput);

Input Validation

import java.util.regex.Pattern;

public class CrlfValidator {
    private static final Pattern CRLF_PATTERN = Pattern.compile("[\\r\\n]");

    public static boolean containsCrlf(String input) {
        return input != null && CRLF_PATTERN.matcher(input).find();
    }

    public static String removeCrlf(String input) {
        if (input == null) return null;
        return CRLF_PATTERN.matcher(input).replaceAll("");
    }

    public static void validateNoCrlf(String input, String paramName) {
        if (containsCrlf(input)) {
            throw new IllegalArgumentException(
                paramName + " contains invalid CRLF characters"
            );
        }
    }
}

// Usage
@GetMapping("/process")
public String process(@RequestParam String userInput) {
    CrlfValidator.validateNoCrlf(userInput, "userInput");
    // Process safely
    return "success";
}

Why this works:

This centralized validation utility demonstrates security best practices by providing reusable CRLF detection and remediation methods throughout your Java application. The static Pattern instance is compiled once when the class loads, providing efficient regex matching across all invocations. The pattern [\\r\\n] matches any carriage return or line feed character in the input, enabling quick detection of CRLF injection attempts. Compiling the pattern as a static field rather than in each method call improves performance in high-throughput applications.

The three methods serve different security strategies. containsCrlf() performs non-destructive detection, useful for conditional logic, security logging, or metrics that track attack attempts without modifying data. removeCrlf() provides sanitization by replacing all CRLF characters with empty strings, returning a cleaned version suitable for use in contexts where you want to preserve as much user input as possible. validateNoCrlf() enforces a strict security policy by throwing an exception if CRLF is detected, implementing the "fail securely" principle where invalid input causes request rejection.

The validation approach (throwing an exception) is particularly important for security-critical operations like setting HTTP headers, creating redirects, or handling authentication data. When CRLF is detected in these contexts, it almost certainly indicates an attack attempt rather than legitimate user input, so the appropriate response is to reject the request entirely and log the incident. The descriptive exception message includes the parameter name, making it easier to identify which input field contained the malicious data during security incident investigation. Use this validator at your application's trust boundaries - typically in controllers or input DTOs - to prevent malicious input from reaching deeper application layers where it might be used in vulnerable contexts.

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Additional Resources