Skip to content

CWE-93: CRLF Injection - C# / ASP.NET

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. This can lead to HTTP Response Splitting, log forgery, or header manipulation attacks.

Primary Defence: Strip or reject newline characters (\r, \n) from all user input before using in HTTP headers or logs, use ASP.NET Core's built-in header validation which automatically rejects invalid header values, validate header values against strict patterns (alphanumeric and safe characters), use structured logging (JSON format) 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
public IActionResult Download(string filename)
{
    Response.Headers.Add("Content-Disposition", $"attachment; filename={filename}");
    return File(fileBytes, "application/octet-stream");
}

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

Unvalidated Redirects

// VULNERABLE
public IActionResult Redirect(string url)
{
    return Redirect(url);  // Can contain CRLF
}

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

Log Injection

// VULNERABLE - Log forgery
public void LogUserAction(string username, string action)
{
    logger.LogInformation($"User {username} performed {action}");
}

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

Secure Patterns

Safe Header Manipulation

// SECURE - Validate and sanitize filename
using System.IO;
using System.Text.RegularExpressions;

public IActionResult Download(string filename)
{
    // Remove any path characters and CRLF
    string safeName = Path.GetFileName(filename);
    safeName = Regex.Replace(safeName, @"[\r\n]", "");

    // Use ContentDispositionHeaderValue for proper encoding
    var contentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = safeName
    };

    Response.Headers.Add("Content-Disposition", contentDisposition.ToString());
    return File(fileBytes, "application/octet-stream");
}

Why this works:

This pattern prevents CRLF injection through a multi-layered defense approach. First, Path.GetFileName() strips any directory traversal components and isolates just the filename portion, removing path separators that could be used in attacks. The regex replacement then explicitly removes any carriage return (\r) or line feed (\n) characters that attackers might inject to split HTTP headers or add malicious headers to the response.

The critical security mechanism is using ASP.NET Core's ContentDispositionHeaderValue class, which is specifically designed to handle header construction safely. This class automatically encodes special characters according to RFC 2183 and RFC 2231 standards, ensuring that the filename value is properly quoted and escaped. When special characters (including spaces, quotes, or international characters) are present, the class applies proper encoding rules that prevent header injection while maintaining filename integrity.

By using the framework's built-in header construction API instead of manual string concatenation, you ensure that the header value conforms to HTTP specifications. The ContentDispositionHeaderValue class handles edge cases like filenames with quotes, semicolons, or Unicode characters that could otherwise break header parsing. This approach is superior to manual sanitization because it's RFC-compliant, handles international characters correctly, and receives security updates as part of the framework. Always prefer framework-provided header APIs over string concatenation when constructing HTTP headers that include user-controlled values.

URL Encoding for Redirects

// SECURE - Validate and encode redirect URLs
using System.Text.Encodings.Web;

public IActionResult SafeRedirect(string returnUrl)
{
    // Validate it's a local URL
    if (!Url.IsLocalUrl(returnUrl))
    {
        return RedirectToAction("Index", "Home");
    }

    // Remove CRLF characters
    returnUrl = returnUrl.Replace("\r", "").Replace("\n", "");

    return Redirect(returnUrl);
}

// SECURE - Use UrlHelper for encoding
public IActionResult RedirectWithParam(string param)
{
    string safeParam = UrlEncoder.Default.Encode(param);
    return Redirect($"/page?value={safeParam}");
}

Why this works:

These redirect patterns prevent HTTP response splitting by combining URL validation with explicit CRLF removal. The Url.IsLocalUrl() method is crucial as it ensures that redirects only go to paths within the same application origin, preventing open redirect vulnerabilities that attackers could chain with CRLF injection. By rejecting any URL that doesn't start with / or starts with // (which browsers interpret as protocol-relative URLs), you prevent redirects to attacker-controlled domains where response splitting could be exploited for phishing or XSS.

The explicit removal of \r and \n characters provides defense-in-depth even after URL validation. While modern ASP.NET Core versions typically sanitize redirect URLs automatically, explicitly removing CRLF characters ensures protection across different framework versions and hosting configurations. This is especially important because attackers might use URL encoding (%0d%0a), Unicode variations, or double-encoding to bypass simple filters, so removing the literal characters acts as a final safety check.

The second pattern demonstrates using UrlEncoder.Default.Encode() for query parameters, which applies proper percent-encoding per RFC 3986. This encoder converts CRLF characters to %0D%0A, preventing them from being interpreted as header delimiters. When constructing redirect URLs with parameters, always encode the parameter values rather than the entire URL - encoding after the scheme and path preserves the URL structure while securing the user-controlled portions. This approach maintains valid redirect functionality while eliminating the possibility of header injection through the Location header.

Safe Logging

// SECURE - Sanitize log entries
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;

public class SecureLogger
{
    private readonly ILogger<SecureLogger> _logger;

    public SecureLogger(ILogger<SecureLogger> logger)
    {
        _logger = logger;
    }

    private string SanitizeForLog(string input)
    {
        if (string.IsNullOrEmpty(input))
            return input;

        // Remove CRLF characters
        return input.Replace("\r", "").Replace("\n", " ").Replace("\t", " ");
    }

    public void LogUserAction(string username, string action)
    {
        string safeUser = SanitizeForLog(username);
        string safeAction = SanitizeForLog(action);

        _logger.LogInformation("User {Username} performed {Action}", safeUser, safeAction);
    }
}

Why this works:

This logging pattern prevents log injection attacks by sanitizing user input before it enters log statements. The SanitizeForLog() method removes carriage return and line feed characters, replacing them with spaces to maintain readability while preventing attackers from injecting fake log entries. Without this sanitization, an attacker could provide input like "admin\r\nINFO: User hacker performed GRANT ADMIN", which would create a completely fabricated log entry that appears legitimate in log analysis tools and SIEM systems.

The pattern uses structured logging with parameter placeholders ({Username}, {Action}) rather than string interpolation or concatenation. While the primary defense is the SanitizeForLog() method, using structured logging provides additional benefits: it separates the log message template from the data, making it easier to parse logs programmatically and ensuring that the message format cannot be altered by user input. Modern logging frameworks render these parameters in a way that makes it clear which parts are data versus template text.

Replacing CRLF characters with spaces (or removing them entirely) preserves the audit trail while preventing injection attacks. This approach maintains the intent of the log message - recording what the user did - without allowing attackers to manipulate log files. For production environments, consider also removing or escaping other control characters and limiting the length of logged user input. The encapsulated SanitizeForLog() method makes it easy to apply consistent sanitization across your entire logging infrastructure, ensuring that no code path accidentally logs unsanitized user input.

Structured Logging (Best Practice)

// SECURE - Use structured logging (prevents log injection)
public class UserService
{
    private readonly ILogger<UserService> _logger;

    public UserService(ILogger<UserService> logger)
    {
        _logger = logger;
    }

    public void RecordLogin(string username, string ipAddress)
    {
        // Structured logging automatically sanitizes parameters
        _logger.LogInformation(
            "Login successful for user {Username} from {IPAddress}",
            username,  // Automatically sanitized
            ipAddress
        );
    }

    public void RecordFailedAttempt(string username, string reason)
    {
        _logger.LogWarning(
            "Login failed for user {Username}. Reason: {Reason}",
            username,
            reason
        );
    }
}

Why this works:

Structured logging in ASP.NET Core provides automatic protection against log injection by treating log parameters as data rather than part of the log message format. When you use parameterized logging like LogInformation("User {Username}", username), the logging framework automatically serializes the username value in a structured format (typically JSON or a delimited format) that cannot break the log entry structure. This means even if username contains \r\n, it will be encoded or escaped in the output rather than creating a new line in the log file.

The key difference from string interpolation ($"User {username}") is that structured logging preserves the distinction between the message template and the data. Log aggregation systems like Elasticsearch, Splunk, or Azure Monitor can parse structured logs to extract the username as a separate field, making searching and analysis more powerful. The logging framework handles the serialization safely, ensuring that special characters in the data don't corrupt the log format or inject false entries.

This pattern represents best practice because it provides security benefits without requiring explicit sanitization code. The framework handles encoding automatically, reducing the risk that developers will forget to sanitize input in some code path. Additionally, structured logging improves observability by making log data queryable and enabling features like filtering by specific usernames or correlating related log entries. While you should still validate and sanitize input at the application boundary, structured logging provides defense-in-depth for logging specifically, ensuring that even if malicious input reaches your logging statements, it cannot corrupt your audit trail.

// SECURE - Safe cookie handling
public void SetSecureCookie(string name, string value)
{
    // Validate cookie name and value
    if (string.IsNullOrWhiteSpace(name) || name.Contains("\r") || name.Contains("\n"))
    {
        throw new ArgumentException("Invalid cookie name", nameof(name));
    }

    value = value?.Replace("\r", "").Replace("\n", "") ?? "";

    Response.Cookies.Append(name, value, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict,
        MaxAge = TimeSpan.FromHours(1)
    });
}

Why this works:

This cookie pattern prevents CRLF injection in Set-Cookie headers through explicit validation before cookie creation. The validation checks ensure that the cookie name doesn't contain carriage return or line feed characters, which attackers could use to inject additional Set-Cookie headers or other HTTP headers in the response. By throwing an exception for invalid names, the code fails securely rather than risking header injection. The value is also sanitized by removing CRLF characters, preventing injection through the cookie value field.

Using ASP.NET Core's CookieOptions class provides secure cookie configuration that goes beyond CRLF protection. The HttpOnly flag prevents JavaScript access to the cookie, mitigating XSS-based cookie theft. The Secure flag ensures the cookie is only transmitted over HTTPS, preventing interception over unencrypted connections. The SameSite = SameSiteMode.Strict setting prevents the cookie from being sent in cross-site requests, protecting against CSRF attacks. Together, these settings create a defense-in-depth approach where even if CRLF injection is attempted, the cookie's security properties limit the damage.

The Response.Cookies.Append() method is the framework's safe API for setting cookies, handling the proper encoding and formatting of cookie headers according to RFC 6265. This method automatically constructs the Set-Cookie header with proper attribute formatting, ensuring that the cookie name, value, and attributes are correctly delimited. By combining input validation with framework-provided cookie APIs and secure cookie attributes, this pattern demonstrates comprehensive protection against both CRLF injection specifically and cookie-based attacks generally. Always validate cookie names and values before setting them, and always use secure cookie attributes in production environments.

Alternative Encoding Methods

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

using System.Text.Encodings.Web;
using System.Web;
using System.Net;

// URL encoding (prevents CRLF in URLs/headers)
string safe = UrlEncoder.Default.Encode(userInput);
string safe2 = HttpUtility.UrlEncode(userInput);
string safe3 = WebUtility.UrlEncode(userInput);

// HTML encoding (for log output display)
string safeHtml = HtmlEncoder.Default.Encode(userInput);
string safeHtml2 = HttpUtility.HtmlEncode(userInput);

// JavaScript encoding (for logs displayed in web contexts)
string safeJs = JavaScriptEncoder.Default.Encode(userInput);
string safeJs2 = HttpUtility.JavaScriptStringEncode(userInput);

Why this works: These encoding utilities provide context-specific protection beyond just CRLF removal. URL encoding converts CRLF characters to %0D%0A, preventing them from being interpreted as header delimiters while maintaining valid URLs. HTML encoding converts special characters to entities for safe display in logs viewed through web interfaces. JavaScript encoding prevents injection in logging systems that display logs in web contexts with script execution. Using the appropriate encoder for each context ensures comprehensive protection - URL encoding for redirects and Location headers, HTML encoding for log output displayed in web UIs, JavaScript encoding for logs embedded in client-side diagnostic tools.

Microsoft AntiXSS Library (Enhanced Protection):

using Microsoft.Security.Application;

string safeUrl = Encoder.UrlEncode(userInput);
string safeHtml = Encoder.HtmlEncode(userInput);
string safeJs = Encoder.JavaScriptEncode(userInput);
string safeHtmlFragment = AntiXss.GetSafeHtmlFragment(userInput);

// NuGet: Install-Package AntiXSS

Input Validation

public class CrlfValidator
{
    private static readonly Regex CrlfPattern = new Regex(@"[\r\n]");

    public static bool ContainsCrlf(string input)
    {
        return !string.IsNullOrEmpty(input) && CrlfPattern.IsMatch(input);
    }

    public static string RemoveCrlf(string input)
    {
        if (string.IsNullOrEmpty(input))
            return input;

        return CrlfPattern.Replace(input, "");
    }

    public static void ValidateNoCrlf(string input, string paramName)
    {
        if (ContainsCrlf(input))
        {
            throw new ArgumentException(
                "Input contains invalid CRLF characters",
                paramName
            );
        }
    }
}

// Usage
public IActionResult ProcessInput(string userInput)
{
    CrlfValidator.ValidateNoCrlf(userInput, nameof(userInput));

    // Process safely
    return Ok();
}

Why this works:

This centralized validation utility provides reusable CRLF detection and removal across your application. The CrlfPattern regex efficiently detects any occurrence of \r or \n characters in a single pass, using a compiled regex for performance in high-throughput scenarios. By encapsulating this logic in a static utility class, you ensure consistent CRLF handling throughout your codebase - developers can call these methods without needing to remember the specific regex pattern or sanitization logic each time.

The three methods serve different validation strategies. ContainsCrlf() performs detection without modification, useful for conditional logic or logging when you need to know if input is malicious. RemoveCrlf() provides sanitization by stripping out the dangerous characters, returning a safe version of the input. ValidateNoCrlf() enforces a strict policy by throwing an exception if CRLF is detected, following the "fail securely" principle - if the input is invalid, the request should be rejected rather than attempting to sanitize it.

The ValidateNoCrlf() method that throws an exception is particularly important for security-critical scenarios like HTTP headers, redirects, or authentication flows where any CRLF presence indicates an attack attempt. By throwing a descriptive ArgumentException with the parameter name, you provide clear error messages for debugging while preventing the malicious input from being processed. This approach is superior to silent sanitization in security contexts because it allows you to log the attack attempt, return a 400 Bad Request to the client, and maintain a strict security posture. Use validation utilities like this at your application's entry points to reject malicious input before it reaches deeper application logic.

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