Skip to content

CWE-113: HTTP Response Splitting

Overview

HTTP Response Splitting occurs when untrusted user input is included in HTTP headers without proper validation or encoding, allowing attackers to inject CRLF characters and create additional headers or responses.

OWASP Classification

A05:2025 - Injection

Risk

High: Attackers can perform web cache poisoning, cross-site scripting (XSS), or redirect users to malicious sites by manipulating HTTP responses.

Remediation Steps

Core principle: Never allow untrusted input to influence HTTP response headers; header values must be validated or constructed by the server so they cannot introduce CRLF characters or alter response structure.

Locate the Vulnerable Header Construction

Review the security findings to identify where untrusted data is used in HTTP headers:

  • Identify the source: Find where untrusted data enters (HTTP parameters, database, external APIs)
  • Trace to the sink: Locate where data is set in HTTP response headers (Location, Set-Cookie, etc.)
  • Check the data flow: Review each frame in the data_path for missing sanitization
  • Understand the context: Determine which header is being set and its security implications

Remove or Encode CR/LF Characters

Sanitize all untrusted data before including in HTTP headers:

  • Remove newline characters: Strip \r (carriage return, %0D) and \n (line feed, %0A) from input
  • Remove null bytes: Also strip \0 (%00) which can terminate strings unexpectedly
  • Encode special characters: URL-encode if the header value permits it
  • Use allowlist validation: For restricted header values (e.g., URLs), validate against expected format

Use Framework-Provided Header APIs

Prefer framework methods that handle header construction safely:

  • Use framework redirect methods: Instead of manually setting Location header, use framework redirect functions
  • Use cookie APIs: Use framework cookie-setting methods instead of manually constructing Set-Cookie headers
  • Avoid manual header construction: Don't concatenate strings to build headers
  • Let framework encode: Modern frameworks often encode header values automatically

Validate Input for Header Fields

Apply strict validation to all data that will be used in headers:

  • Validate URLs: For Location headers, verify the URL is properly formatted and uses allowed schemes (http, https)
  • Check length limits: Enforce maximum length for header values
  • Use allowlists: For enumerated header values, validate against known-good list
  • Reject unexpected characters: Block or encode characters that shouldn't appear in the specific header type

Monitor and Log Header Manipulation Attempts

Detect and alert on potential HTTP response splitting attacks:

  • Log all header values: Record what data is being set in headers
  • Alert on suspicious patterns: Monitor for CR/LF characters, multiple header separators, null bytes
  • Track injection attempts: Log when validation rejects input containing newlines
  • Implement rate limiting: Prevent automated attack attempts

Test with HTTP Response Splitting Payloads

Verify the fix prevents header injection:

  • Test with CR/LF characters: Try input like value%0d%0aInjected-Header: malicious
  • Test double encoding: Try %0d%0a (URL encoded) and %250d%250a (double encoded)
  • Test header injection: Attempt to inject Set-Cookie or other security-sensitive headers
  • Test response splitting: Try to inject a complete HTTP response with %0d%0a%0d%0a<html>...
  • Verify legitimate redirects work: Ensure valid URLs still function correctly

Dynamic Scan Guidance

For guidance on remediating this CWE when detected by dynamic (DAST) scanners:

Common Vulnerable Patterns

Directly inserting user input into HTTP headers

# Dangerous: user input in header
response.headers['Location'] = user_input

Why this is vulnerable:

  • Failing to validate or encode header values

Secure Patterns

Use Framework Redirect APIs (Best Practice)

# BEST: Use framework's redirect method (handles encoding automatically)
# Framework methods typically validate and encode header values
return redirect(validated_url)

Why this works: Modern framework redirect methods (like Response.redirect(), res.redirect(), redirect()) handle header construction safely by automatically encoding special characters, validating input, and preventing header injection. They construct the Location header internally without allowing raw user input to contaminate the header structure. This is the preferred approach because frameworks have battle-tested implementations that handle edge cases like null bytes, Unicode normalization, and encoding bypasses.

Validate and Sanitize for Header Context

# GOOD: Validate + sanitize based on header type
def safe_redirect(user_url):
    # 1. Remove dangerous characters (necessary but not sufficient)
    cleaned = user_url.replace('\r', '').replace('\n', '').replace('\0', '')

    # 2. Validate based on header context
    if header_is_location:
        # For Location headers, validate as URL (see CWE-601)
        if not is_safe_url(cleaned):
            return default_safe_url
    elif header_is_cookie:
        # For Set-Cookie, validate cookie value format
        if not is_valid_cookie_value(cleaned):
            return None

    # 3. Use framework API to set header (additional safety layer)
    response.set_header('Location', cleaned)
    return response

Why this works: This defense-in-depth approach combines multiple protections: (1) Removing CR/LF (\r, \n) and null bytes (\0) prevents basic header injection by eliminating characters used to create new headers or terminate strings. (2) Context-specific validation ensures the value is appropriate for the header type - Location headers should be valid URLs (validated per CWE-601 guidance), Set-Cookie values should match cookie syntax, custom headers should match expected patterns. (3) Using framework APIs to set headers (rather than manual string concatenation) provides an additional safety layer as frameworks often apply their own encoding. (4) Fail-closed behavior (returning default or None) ensures invalid input doesn't result in broken headers. This multi-layered approach prevents not just CRLF injection but also related attacks like open redirects (CWE-601) and cookie injection.

Manual Sanitization (When Framework APIs Unavailable)

# ACCEPTABLE: Manual sanitization if framework methods not available
def sanitize_header_value(value, header_name):
    # Remove CRLF and null bytes
    sanitized = value.replace('\r', '').replace('\n', '').replace('\0', '')

    # Remove other control characters (ASCII 0-31 except space)
    sanitized = ''.join(c for c in sanitized if ord(c) >= 32 or c == ' ')

    # Header-specific validation
    if header_name == 'Location':
        # Validate URL format and restrict to local URLs
        if not is_local_url(sanitized):
            return '/'  # Default safe redirect

    return sanitized

# Usage
clean_value = sanitize_header_value(user_input, 'Location')
response.headers['Location'] = clean_value

Why this works: When framework methods aren't available, comprehensive manual sanitization is required. Removing CR (\r), LF (\n), and null bytes (\0) prevents header injection attacks. Additionally removing all control characters (ASCII 0-31) blocks other potential injection vectors like tab characters, form feeds, or vertical tabs that might be interpreted differently by proxies or clients. The header-specific validation (like is_local_url for Location headers) prevents related vulnerabilities - for Location headers this prevents open redirects (CWE-601). The fail-safe default ensures that invalid input results in a safe, known-good value rather than a broken or exploitable header. This pattern should only be used when modern framework APIs are truly unavailable.

Language-Specific Guidance

  • C# - Response.Redirect, ContentDisposition.builder, header validation
  • Java - Spring redirect methods, ResponseCookie.from, UriComponentsBuilder

Additional Resources