Weakness reference
CWE-444

Inconsistent Interpretation of HTTP Requests (Request Smuggling)

HTTP request smuggling occurs when a proxy, load balancer, or firewall interprets the boundaries of an HTTP request differently than the backend server it…

01Summary

HTTP request smuggling occurs when a proxy, load balancer, or firewall interprets the boundaries of an HTTP request differently than the backend server it forwards to. An attacker can exploit this gap to inject a hidden request that bypasses security controls, reaches the backend undetected, or poisons a shared cache. This is a network-layer vulnerability that affects the entire request pipeline, not just a single application.

02How It Happens

HTTP request boundaries are defined by headers like Content-Length and Transfer-Encoding. When these headers are ambiguous, conflicting, or malformed, different systems may disagree on where one request ends and the next begins. A proxy might see two separate requests while the backend sees one, or vice versa. An attacker crafts a request with intentionally ambiguous framing—for example, both Content-Length and Transfer-Encoding: chunked headers, or duplicate headers with conflicting values. The proxy forwards what it believes is a complete request, but the backend parses it differently, allowing the attacker's injected payload to be processed as a separate request or to modify how the next legitimate request is interpreted.

03Real-World Impact

Request smuggling can lead to cache poisoning (serving malicious content to other users), session fixation, credential theft, and bypass of authentication or WAF rules. An attacker might inject a request that the proxy's security filter doesn't see, reaching the backend directly. In shared hosting or multi-tenant environments, one user's smuggled request can affect another user's session or data. The vulnerability is particularly dangerous because it operates below the application layer—no amount of input validation in the web app itself can prevent it.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import socket

# Backend server receiving raw HTTP from a proxy
def handle_request(client_socket):
    raw_data = client_socket.recv(4096)
    
    # Naive parsing: assumes Content-Length is authoritative
    headers_end = raw_data.find(b'\r\n\r\n')
    headers = raw_data[:headers_end].decode()
    
    content_length = int([h.split(': ')[1] for h in headers.split('\r\n') 
                          if h.startswith('Content-Length')][0])
    
    body = raw_data[headers_end + 4:headers_end + 4 + content_length]
    process_request(headers, body)

Why it's vulnerable:
The code extracts Content-Length without checking for conflicting Transfer-Encoding headers or duplicate headers. If a proxy uses Transfer-Encoding to parse the request boundary but the backend uses Content-Length, the two will disagree on where the request ends, allowing smuggling.

Fixed pattern
import socket

def handle_request(client_socket):
    raw_data = client_socket.recv(4096)
    
    # RFC 7230: Transfer-Encoding takes precedence over Content-Length
    # Reject requests with both headers or conflicting values
    headers_end = raw_data.find(b'\r\n\r\n')
    headers = raw_data[:headers_end].decode()
    
    has_transfer_encoding = any(h.startswith('Transfer-Encoding') for h in headers.split('\r\n'))
    has_content_length = any(h.startswith('Content-Length') for h in headers.split('\r\n'))
    
    if has_transfer_encoding and has_content_length:
        # Reject ambiguous requests
        return error_response(400, "Conflicting Content-Length and Transfer-Encoding")
    
    # Use Transfer-Encoding if present, otherwise Content-Length
    if has_transfer_encoding:
        body = parse_chunked_encoding(raw_data[headers_end + 4:])
    else:
        content_length = int([h.split(': ')[1] for h in headers.split('\r\n') 
                              if h.startswith('Content-Length')][0])
        body = raw_data[headers_end + 4:headers_end + 4 + content_length]
    
    process_request(headers, body)
Vulnerable pattern
<?php
// Naive HTTP request parsing in a proxy or gateway
$raw_request = file_get_contents('php://input');
$parts = explode("\r\n\r\n", $raw_request, 2);
$headers = $parts[0];
$body = $parts[1] ?? '';

// Extract Content-Length without checking for Transfer-Encoding
preg_match('/Content-Length: (\d+)/i', $headers, $matches);
$content_length = $matches[1] ?? 0;

$request_body = substr($body, 0, $content_length);
forward_to_backend($headers, $request_body);
?>

Why it's vulnerable:
The code uses only Content-Length to determine request boundaries and ignores Transfer-Encoding. If both headers are present with conflicting values, the proxy and backend will parse the request differently.

Fixed pattern
<?php
// Strict HTTP request parsing following RFC 7230
$raw_request = file_get_contents('php://input');
$parts = explode("\r\n\r\n", $raw_request, 2);
$headers = $parts[0];
$body = $parts[1] ?? '';

$has_transfer_encoding = preg_match('/Transfer-Encoding:/i', $headers);
$has_content_length = preg_match('/Content-Length: (\d+)/i', $headers, $cl_matches);

// Reject ambiguous requests
if ($has_transfer_encoding && $has_content_length) {
    http_response_code(400);
    die("Bad Request: Conflicting Content-Length and Transfer-Encoding");
}

// Transfer-Encoding takes precedence per RFC 7230
if ($has_transfer_encoding) {
    $request_body = parse_chunked_body($body);
} else if ($has_content_length) {
    $content_length = (int)$cl_matches[1];
    $request_body = substr($body, 0, $content_length);
} else {
    $request_body = '';
}

forward_to_backend($headers, $request_body);
?>

05Prevention Checklist

Enforce RFC 7230 strictly:
Reject any request with both Content-Length and Transfer-Encoding headers, or with duplicate headers that conflict.
Normalize header parsing:
Use a well-tested HTTP library (e.g., http-parser, hyper, or framework-native parsers) rather than custom regex or string splitting.
Align proxy and backend:
Ensure your proxy, load balancer, and backend server all use the same HTTP parsing rules. Test with intentionally malformed requests.
Disable HTTP/1.0 pipelining:
If not needed, disable request pipelining to reduce the attack surface.
Monitor for suspicious patterns:
Log requests with conflicting headers, unusual Transfer-Encoding values, or requests that appear to contain embedded HTTP.
Use HTTP/2 or HTTP/3 where possible:
These protocols have clearer framing and are less susceptible to smuggling.

06Signs You May Already Be Affected

Look for unusual cache behavior (serving unexpected content to users), unexpected requests in backend logs that don't appear in proxy logs, or WAF bypass attempts in your logs. If you see requests with both Content-Length and Transfer-Encoding headers reaching your backend, or if your proxy and backend disagree on request counts, investigate immediately.

07Related Recent Vulnerabilities