Web Security

What Is HTTP Request Smuggling? Request Smuggling Explained

A complete technical guide to HTTP request smuggling: how discrepancies between front-end and back-end parsers let attackers smuggle requests through security controls.

Advanced14 min·Nyxeara Security Research·2026-09-16·CWE-444
http-request-smugglingweb-securityhttpproxy-bypass
Prerequisites
what-is-http

Short answer

HTTP request smuggling is an attack that exploits differences in how a front-end server (like a load balancer or reverse proxy) and a back-end server parse the boundaries between HTTP requests. The attacker crafts an ambiguous message that the front-end treats as one request but the back-end splits into two, allowing the second "smuggled" request to hijack another user's session or bypass security controls.

The idea in one minute

Imagine a train station with two ticket inspectors. The first inspector checks tickets at the platform entrance. The second inspector checks tickets again at the train door. Both inspectors look at the same ticket, but they read it slightly differently.

The first inspector reads "One adult, one child" and lets both through. The second inspector reads "One adult" and stops there — the "one child" part is hidden in the fold of the ticket, visible only to the first inspector.

Now imagine two passengers. The first is an attacker who hands over a ticket cleverly folded. The first inspector reads the ticket as "One adult" and lets the attacker through. The second inspector reads the same ticket differently: "One adult, one child." The second inspector now believes a second passenger (the "child") has already been checked and can board without showing a ticket. The attacker has smuggled a second passenger onto the train.

HTTP request smuggling works the same way. A front-end server (nginx, HAProxy, an AWS ALB) and a back-end server (Apache, Tomcat, a Node.js process) parse HTTP request boundaries using headers like Content-Length and Transfer-Encoding. When these two servers disagree on where one request ends and the next begins, an attacker can craft a request whose body the front-end considers complete but the back-end considers partial. The leftover bytes become the start of the next request — a smuggled request that arrives at the back-end without the front-end having inspected it.

How HTTP request smuggling actually works

HTTP requests are delimited by one of two mechanisms. The Content-Length header specifies the body size in bytes. The Transfer-Encoding: chunked header uses a self-delimiting format where each chunk starts with its size in hex, followed by the chunk data, ending with a zero-length chunk.

The HTTP specification (RFC 7230) says that when both headers are present, Transfer-Encoding takes precedence. But not all servers implement this correctly. Some ignore Transfer-Encoding and use Content-Length. Some use the first header they see. Some concatenate both interpretations. These discrepancies are the root of every smuggling attack.

There are five well-known attack classes. Each exploits a different parsing disagreement:

CL.TE (Content-Length → Transfer-Encoding). The front-end uses Content-Length, but the back-end uses Transfer-Encoding. The attacker sends a request with both headers. The front-end reads Content-Length bytes (a short body) and forwards the message. The back-end sees Transfer-Encoding: chunked, reads chunks until the zero-length terminator, and treats everything after it as the next request — which the attacker has appended as a partial smuggled prefix.

TE.CL (Transfer-Encoding → Content-Length). The reverse: the front-end uses Transfer-Encoding (chunked), but the back-end falls back to Content-Length. The attacker sends a chunked body that the front-end consumes entirely, but the back-end reads only Content-Length bytes, leaving the rest of the chunk data as the smuggled prefix.

TE.TE (Transfer-Encoding → Transfer-Encoding, with obfuscation). Both servers use Transfer-Encoding, but one can be tricked into ignoring it by obfuscating the header. The attacker sends Transfer-Encoding: xchunked or Transfer-Encoding : chunked (with a space before the colon) — one server ignores the malformed header and falls back to Content-Length, while the other parses it normally.

Content-Length concatenation. When two Content-Length headers are present, some servers use the first, others the last, and some reject the request. An attacker can align a request that one server considers one byte shorter than the other.

HTTP/2 downgrade smuggling. When a front-end accepts HTTP/2 connections but the back-end speaks HTTP/1.1, the translation between protocols introduces ambiguity. HTTP/2 has no Content-Length vs. Transfer-Encoding conflict — it frames messages natively — but the translation to HTTP/1.1 can reintroduce parsing differences.

The request flow

The classic CL.TE attack visualized:

Front-end (uses Content-Length)          Back-end (uses Transfer-Encoding)
─────────────────────────────────         ─────────────────────────────────

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

0                                        0

POST /admin/delete HTTP/1.1              POST /admin/delete HTTP/1.1
Host: vulnerable.com                     Host: vulnerable.com
Content-Length: 10                       Content-Length: 10

x=1                                      x=1

                                         ↑ This second request is "smuggled"
                                         — the front-end never saw it

The front-end reads Content-Length: 30 and forwards exactly 30 bytes of body. Those 30 bytes are:

0\r\n
\r\n
POST /admin/delete HTTP/1.1\r\n
Host: vulnerable.com\r\n
Content-Length: 10\r\n
\r\n
x=1

The back-end processes the chunked body: 0\r\n is the zero-length chunk terminator, so the back-end considers the first request complete after the chunk end. Everything after it — the POST /admin/delete — becomes a new request that was never inspected by the front-end.

As a sequence:

sequenceDiagram
    participant Attacker
    participant Front as Front-end (CL)
    participant Back as Back-end (TE)

    Attacker->>Front: POST / with Content-Length: 30 + Transfer-Encoding: chunked
    Note over Front: Reads 30 bytes of body (includes smuggled request)
    Front->>Back: Forwards the 30 bytes
    Note over Back: Parses Transfer-Encoding: chunked<br/>Zero-length chunk ends at byte ~15
    Note over Back: Remaining bytes = smuggled request
    Back->>Back: Processes POST /admin/delete as new request
    Note over Front: Front-end never inspected /admin/delete

A minimal example

A smuggling probe using CL.TE. This detects whether the back-end honors Transfer-Encoding over Content-Length:

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

0

G

The front-end reads Content-Length: 6 — six bytes of body are 0\r\n\r\nG. It forwards this to the back-end. The back-end reads Transfer-Encoding: chunked: the chunk 0 ends the first request, then \r\n\r\nG is the start of a new request — but G is not a valid HTTP method, so the back-end returns an error. If the front-end returns the back-end's error response, the attack surface is confirmed.

Here is a Python script that tests for CL.TE smuggling:

import socket

def probe_clte(host: str, port: int) -> bool:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect((host, port))

    payload = (
        "POST / HTTP/1.1\r\n"
        f"Host: {host}\r\n"
        "Content-Length: 6\r\n"
        "Transfer-Encoding: chunked\r\n"
        "\r\n"
        "0\r\n"
        "\r\n"
        "G"
    )
    sock.send(payload.encode())
    response = sock.recv(4096).decode()
    sock.close()

    # If the back-end processed "G" as a method, we get a 400 or similar error
    return "400" in response or "Bad Request" in response or "Unrecognized" in response

print(probe_clte("target.com", 80))

A vulnerable server returns 400 Bad Request or similar — confirming that the back-end received G as a new request.

Exploitation requires a follow-up request to hijack a victim's connection. The attacker sends a carefully aligned first request whose smuggled portion poisons the connection for the next user. When the victim's request arrives on the same TCP connection, the server prepends the smuggled prefix to it, effectively injecting the attacker's request ahead of the victim's:

def smuggle_request(host: str, port: int, smuggled_request: str) -> bytes:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect((host, port))

    smuggled_hex = len(smuggled_request.encode())
    prefix_len = smuggled_hex + 8  # account for \r\n after chunk size

    payload = (
        f"POST / HTTP/1.1\r\n"
        f"Host: {host}\r\n"
        f"Content-Length: {prefix_len}\r\n"
        "Transfer-Encoding: chunked\r\n"
        "\r\n"
        f"{smuggled_hex:x}\r\n"
        f"{smuggled_request}\r\n"
        "0\r\n"
        "\r\n"
    )

    sock.send(payload.encode())

    # Send a normal request to trigger the smuggled prefix
    normal = (
        f"GET / HTTP/1.1\r\n"
        f"Host: {host}\r\n"
        "\r\n"
    )
    sock.send(normal.encode())
    response = sock.recv(4096)
    sock.close()
    return response

The fix is server-side configuration. Disabling reuse of back-end connections is the nuclear option — every request gets a fresh connection, eliminating the attack surface at a performance cost. The better fix is to ensure the front-end and back-end agree on request boundary parsing:

# nginx — strip Transfer-Encoding from upstream requests
# or ensure proxy_http_version is 1.1
location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

For Apache back-ends, setting RequestReadTimeout and rejecting malformed headers reduces the attack surface:

<IfModule reqtimeout_module>
    RequestReadTimeout header=20-40,minrate=500
    RequestReadTimeout body=20-40,minrate=500
</IfModule>

The most reliable mitigation is to reject requests with ambiguous headers at the front-end proxy level before they reach the back-end at all.

Why the vulnerability exists

  • HTTP has two competing mechanisms for delimiting request bodies (Content-Length and Transfer-Encoding), and the specification's precedence rule is not uniformly implemented. Every server stack makes different parsing decisions, creating a combinatorial space of disagreements
  • Front-end and back-end servers are often developed and configured by different teams, and neither team considers whether their parsing logic aligns
  • The attack is invisible in application logs. The smuggled request is processed by the back-end but never logged by the front-end — there is no record of it in the security appliance or WAF
  • Connection reuse (HTTP keep-alive) is essential for performance but is the mechanism that makes smuggling exploitable. Without reused connections, each smuggled request would start its own TCP stream and there would be no victim to hijack
  • HTTP/2 to HTTP/1.1 translation introduces new classes of smuggling that were not possible with HTTP/1.1 alone, and many reverse proxies have shipped with translation bugs
  • The attack class was publicly documented in 2005 (by Linhart et al.) but remains widespread because fixing it requires coordinated changes across multiple infrastructure layers, and most teams only control one layer

What attackers look for

  • Proxy chains — any architecture where a front-end proxy forwards to a back-end server over HTTP/1.1. Common in cloud architectures: AWS ALB → nginx → Tomcat, Cloudflare → origin server, Varnish → Apache
  • WAF bypass opportunities — if the front-end runs a WAF and the back-end does not, a smuggled request bypasses all WAF rules. The attacker can send SQL injection, path traversal, or command injection payloads in the smuggled request
  • Internal endpoints — /admin, /api/internal, /debug, /actuator, /health, /metrics — these are often accessible from the back-end but blocked by the front-end. Smuggling allows direct access without front-end restrictions
  • User session hijacking — by smuggling a request that sets a Host header or manipulates a session cookie, the attacker can poison a shared connection and have a subsequent user's request processed in the context of the attacker's payload
  • Reflected XSS amplification — smuggle a request with a malicious payload in the query string, and when the next user's request arrives, the server responds to the smuggled request instead of the user's, serving the XSS payload to the victim

Detection

  • Timing-based detection: send a probe that includes both Content-Length and Transfer-Encoding headers, and measure response time. If the back-end processes the smuggled prefix, the response to a subsequent request may be delayed or malformed compared to a clean baseline
  • Error-based detection: send a probe with an invalid HTTP method (G) in the smuggled position, as in the minimal example above. If the server returns an error for the smuggled method, the attack surface exists
  • Differential testing: send identical request pairs with slight variations (obfuscated Transfer-Encoding, duplicate Content-Length, TE.CL mismatch) and compare responses. Any difference in behavior between pairs indicates a parsing discrepancy
  • HTTP/2 downgrade probing: send requests over HTTP/2 and observe whether the translated HTTP/1.1 request differs from what a direct HTTP/1.1 client would send. Mismatches in headers like :scheme → Content-Length translation are common smuggling vectors
  • Connection-pool poisoning: open a persistent connection, send a smuggled prefix followed by a normal request, and observe whether the response to the normal request is actually a response to the smuggled prefix. If the response content changes, the connection was poisoned and a victim would have been hijacked

Verification: real vulnerability or false positive?

A finding is confirmed when:

  • A probe request with conflicting Content-Length and Transfer-Encoding headers produces a different response pattern (timing, status code, body content) than a clean request, and the difference indicates the back-end processed data beyond what the front-end intended
  • A smuggled request prefix followed by a legitimate user request results in the user receiving a response to the smuggled prefix — demonstrable by injecting a request that returns a unique string and observing that string in a subsequent response
  • The smuggled request reaches an internal endpoint that should be inaccessible from the front-end (e.g., /admin returns 200 when smuggled but 403 when sent directly through the front-end)
  • An HTTP/2 to HTTP/1.1 translation produces a request that, when replayed over HTTP/1.1 directly to the back-end, results in different parsing behavior

A timing-based probe that returns a delayed response but no evidence of actual request splitting is not a confirmed finding — it may indicate a parsing difference that is irrelevant in practice. Confirmation requires demonstrating that the smuggled data is processed as a separate request by the back-end.

Real-world impact

HTTP request smuggling has been used in attacks against major platforms including PayPal, Airbnb, Uber, and Atlassian. Its impact spans the full range of web application compromise:

  • WAF bypass: a smuggled request bypasses every rule the front-end WAF enforces. SQL injection, command injection, path traversal — any attack blocked by the WAF can be smuggled to the back-end where no inspection occurs
  • Session hijacking: by poisoning a connection pool, an attacker can cause their smuggled request to be processed in the context of the next victim user's session. The victim's authentication state is invisible to the attacker, but the attacker controls the operation performed under that identity
  • Internal endpoint access: most architectures restrict access to /admin and /api/internal at the front-end layer. A smuggled request arrives at the back-end without passing through this restriction, directly accessing administrative functions
  • Cache poisoning: by smuggling a request that causes the back-end to return a crafted response, an attacker can poison a shared cache and serve malicious content to every user who requests the cached resource
  • Reflected XSS at scale: smuggle a request with a JavaScript payload in the query string, and every user whose request arrives on the same TCP connection receives the XSS response. Unlike conventional reflected XSS (which requires the victim to click a crafted link), smuggling-based XSS is triggered simply by the victim making any request on the poisoned connection

The most severe attacks combine smuggling with another vulnerability class — using smuggling to bypass a WAF, then exploiting SQL injection or RCE on the back-end — effectively multiplying the severity of both vulnerabilities.

Prevention

  • Disable back-end connection reuse (the "nuclear option"): configure the front-end proxy to send each request over a fresh TCP connection to the back-end. This eliminates smuggling entirely but increases latency and connection overhead. Acceptable for low-traffic applications; prohibitive at scale
  • Use HTTP/2 end-to-end: HTTP/2's framing protocol eliminates the Content-Length vs. Transfer-Encoding ambiguity at the protocol level. If both front-end and back-end speak HTTP/2, the entire attack class is structurally impossible
  • Normalize ambiguous headers at the front-end: configure the proxy to strip Transfer-Encoding from inbound requests and recompute Content-Length server-side. nginx with proxy_http_version 1.1; and a body filter that removes duplicate or conflicting headers is effective
  • Reject conflicting headers outright: a request that contains both Content-Length and Transfer-Encoding should be rejected with a 400 Bad Request. This is the safest default — the legitimate use case for both headers is vanishingly rare
  • Upgrade all servers in the chain to current versions: many smuggling variants have been patched in recent releases of nginx, Apache, HAProxy, AWS ALB, and Tomcat. Keeping versions current eliminates known parser discrepancies
  • Validate HTTP/2 → HTTP/1.1 translation: if your architecture translates between protocols, test the translation layer explicitly. Common bugs include translation of pseudo-headers into body content, and incorrect handling of trailers
  • Monitor for malformed requests: log and alert on requests with duplicate or conflicting Content-Length headers, obfuscated Transfer-Encoding values, or unusual chunked encoding patterns. Even if your servers are patched, monitoring reveals reconnaissance attempts

Related vulnerabilities

  • CRLF injection — smuggling payloads often use CRLF sequences to separate headers. CRLF injection in a request parameter can achieve the same effect as smuggling by injecting header boundaries into the response
  • SSRF — smuggling can be used to reach internal services that would otherwise require SSRF. Conversely, an SSRF vulnerability can be used to probe for smuggling by sending crafted requests to an internal proxy chain
  • XSS — smuggling-based XSS is a distinct attack vector from standard reflected or stored XSS. It does not require the victim to click a crafted link — any request on a poisoned connection triggers the XSS
  • Cache poisoning — HTTP request smuggling is one of several techniques for web cache deception. Combining smuggling with cache poisoning allows an attacker to serve malicious content to broad audiences without targeting individual connections

Testing methodology (do this safely)

  • Set up a test environment that mirrors your production proxy chain. Test on your own infrastructure only — smuggling probes on third-party systems are intrusive and can cause denial of service.
  • Begin with timing-based probes: send 100 clean requests and record baseline response times, then send 100 probe requests with conflicting headers. Any statistically significant increase in response time or error rate warrants deeper investigation.
  • Use a connection-pool exhaustion technique: open and close multiple TCP connections while sending smuggling probes to increase the probability of landing your smuggled prefix on a connection that a test client will reuse.
  • For each discovered discrepancy (CL.TE, TE.CL, TE.TE), attempt to confirm by smuggling a simple request that returns a unique string, then observe that string in a subsequent response on the same connection.
  • Document the exact proxy chain, the front-end and back-end software versions, the probe headers used, and the response that confirmed the split. Include the raw HTTP of both the primary and smuggled requests.
  • After testing, verify that the connection pool is clean by sending a known-good request and confirming it receives the expected response.

Further reading

  • PortSwigger Web Security Academy: HTTP Request Smuggling
  • MITRE: CWE-444 — Inconsistent Interpretation of HTTP Requests ('HTTP Request Smuggling')
  • RFC 7230 — HTTP/1.1 Message Syntax and Routing
  • HTTP Request Smuggling (Linhart, Klein, Heled, Orrin, 2005) — the original research paper
  • PortSwigger Research: HTTP/2 Request Smuggling

Nyxeara perspective

HTTP request smuggling remains one of the most under-detected attack classes in production environments because it lives in the infrastructure layer, not the application layer. Application security scanners send requests to the front-end and inspect responses — they rarely open persistent TCP connections and probe the parsing behavior of the front-end/back-end boundary. Our approach at Nyxeara treats every proxy chain as a candidate for smuggling testing, with particular attention to mixed-version architectures where an HTTP/2-capable front-end forwards to an HTTP/1.1 back-end. The TE.TE obfuscation class is especially fertile ground: front-end libraries that parse Transfer-Encoding with a regex are often tricked by trailing whitespace, tabs between the header name and colon, or Unicode lookalike characters. Each obfuscation technique creates a new parsing divergence, and the combination of proxy version X and back-end version Y produces a combinatorial space that no single scanner exhausts. Manual analysis of the specific software versions and their known parsing behaviors remains the most reliable detection strategy.

Published 2026-09-16 · Updated 2026-09-16