HTTP Response Splitting

ID

swift.http_splitting

Severity

critical

Resource

Injection

Language

Swift

Tags

CWE:113, NIST.SP.800-53, OWASP:2021:A03, PCI-DSS:6.5.1

Description

Improper Neutralization of CR/LF Sequences in HTTP Headers ('HTTP Request/Response Splitting').

HTTP response splitting occurs when user input is unsafely incorporated into HTTP response headers. Attackers exploit these vulnerabilities by injecting CR/LF (Carriage Return plus Line Feed) characters, effectively splitting the HTTP response into two separate responses.

This vulnerability is often referred to as HTTP Header Manipulation.

In iOS, macOS, and server-side Swift applications, HTTP response splitting vulnerabilities commonly occur when:

  • URLRequest header methods (setValue, addValue) receive user-controlled input.

  • Vapor Response headers are constructed from external data.

  • HTTPURLResponse objects are created with untrusted header dictionaries.

  • URLComponents query/fragment properties contain unescaped CRLF sequences.

Rationale

HTTP response splitting can lead to serious security concerns such as:

  • Cross-Site Scripting (XSS): Leveraging the split response to inject script content into another user’s browser.

  • Web Cache Poisoning: Exploiting vulnerable proxy servers or caching mechanisms to cache a poisoned response.

  • Session Fixation: Forcing a user into a session where the session ID is specified by the attacker.

Mitigating this requires diligent input validation and proper handling of HTTP headers to ensure that malicious headers cannot be injected.

The following example demonstrates HTTP Response Splitting in a Vapor web application:

import Vapor

func downloadFileHandler(req: Request) throws -> Response {
    // User provides filename via query parameter
    let filename = try req.query.get(String.self, at: "filename")

    let response = Response()

    // VULNERABLE: User input directly in Content-Disposition header
    response.headers.add(name: "Content-Disposition",
                        value: "attachment; filename=\(filename)")

    return response
}

If an attacker provides the filename:

test.pdf%0d%0aSet-Cookie:%20session=malicious

The decoded CRLF (\r\n) would result in:

Content-Disposition: attachment; filename=test.pdf
Set-Cookie: session=malicious

This allows the attacker to inject a Set-Cookie header, potentially fixating the user’s session.

Another vulnerable example using URLRequest in iOS:

import Foundation

func makeAPIRequest(customHeaderValue: String) {
    var request = URLRequest(url: URL(string: "https://api.example.com")!)

    // VULNERABLE: User input in header value
    request.setValue(customHeaderValue, forHTTPHeaderField: "X-Custom-Header")

    URLSession.shared.dataTask(with: request) { data, response, error in
        // Process response
    }.resume()
}

If customHeaderValue contains "value\r\nMalicious-Header: injected", the attacker can inject arbitrary headers.

HTTPURLResponse construction vulnerability:

import Foundation

func createCustomResponse(userHeaders: [String: String]) -> HTTPURLResponse? {
    // VULNERABLE: Headers dictionary from user input
    return HTTPURLResponse(
        url: URL(string: "https://example.com")!,
        statusCode: 200,
        httpVersion: "HTTP/1.1",
        headerFields: userHeaders  // Attacker controls entire dictionary
    )
}

Remediation

If it is not possible to use untrusted input in HTTP headers, consider the following strategies to avoid HTTP response splitting:

  1. Input Validation: Ensure all user inputs are validated and sanitized before inclusion in HTTP response headers. Either filter out or encode CR/LF characters, or better use a strict whitelist validation to enforce the expected data format for the header.

  2. Output Encoding: Properly encode or replace any special characters in user input embedded within HTTP headers.

  3. Library and Framework Use: Utilize security libraries for validation. Some frameworks include built-in protection against CR/LF injection in HTTP headers.

  4. Security Testing: Implement automatic security testing mechanisms such as Static Application Security Testing (SAST) tools to detect HTTP response splitting vulnerabilities during the development phase.

Examples for fixing HTTP splitting vulnerabilities in Swift follow:

Option 1: Sanitize CR/LF Characters

import Vapor

func downloadFileHandlerSafe(req: Request) throws -> Response {
    let filename = try req.query.get(String.self, at: "filename")

    // FIXED: Remove CRLF sequences
    let safeFilename = filename
        .replacingOccurrences(of: "\r", with: "")
        .replacingOccurrences(of: "\n", with: "")

    let response = Response()
    response.headers.add(name: "Content-Disposition",
                        value: "attachment; filename=\(safeFilename)")

    return response
}

Option 2: Use Percent Encoding for URLRequest

import Foundation

func makeAPIRequestSafe(customHeaderValue: String) {
    var request = URLRequest(url: URL(string: "https://api.example.com")!)

    // FIXED: Percent-encode the value to escape special characters
    if let encoded = customHeaderValue.addingPercentEncoding(
        withAllowedCharacters: .urlHostAllowed) {
        request.setValue(encoded, forHTTPHeaderField: "X-Custom-Header")
    }

    URLSession.shared.dataTask(with: request) { data, response, error in
        // Process response
    }.resume()
}

Option 3: Validate Input Against Whitelist

import Vapor

func setCustomHeaderSafe(req: Request) throws -> Response {
    let headerValue = try req.query.get(String.self, at: "value")

    // FIXED: Validate against whitelist pattern
    let allowedPattern = "^[a-zA-Z0-9._-]+$"
    guard headerValue.range(of: allowedPattern, options: .regularExpression) != nil else {
        throw Abort(.badRequest, reason: "Invalid header value")
    }

    let response = Response()
    response.headers.add(name: "X-Custom-Header", value: headerValue)

    return response
}

Option 4: Use Built-in Encoding Methods

import Foundation

func addSafeHeader(request: inout URLRequest, value: String) {
    // FIXED: Use URLComponents for proper encoding
    var components = URLComponents()
    components.queryItems = [URLQueryItem(name: "value", value: value)]

    if let encoded = components.query {
        request.setValue(encoded, forHTTPHeaderField: "X-Encoded-Value")
    }
}

iOS/macOS Specific Considerations

On iOS and macOS, URLRequest automatically performs some encoding, but:

  • Manual header manipulation via setValue/addValue bypasses automatic encoding

  • Custom URLProtocol implementations may not validate headers

  • Server-side Swift (Vapor) requires explicit validation

Always validate and sanitize user input regardless of framework protections.

Configuration

The detector has the following configurable parameters:

  • sources, that indicates the source kinds to check.

  • neutralizations, that indicates the neutralization kinds to check.

Unless you need to change the default behavior, you typically do not need to configure this detector.

References