Web Security

HTTP Request Smuggling: How It Works and How to Prevent It

A technical deep dive into HTTP request smuggling—CL.TE and TE.CL variants, how ambiguous HTTP parsing allows attackers to poison request queues, why the vulnerability is so impactful, detecting it with Burp Suite, and the configuration changes that prevent it.

March 1, 20269 min readShipSafer Team

What Is HTTP Request Smuggling?

HTTP request smuggling is a class of vulnerability that arises when a front-end server (a load balancer, reverse proxy, or CDN) and a back-end server disagree about where one HTTP request ends and the next begins. This disagreement allows an attacker to "smuggle" a partial HTTP request into the body of a legitimate request, where the back-end server interprets it as the beginning of a new request.

The consequences are severe and varied: bypassing access controls, hijacking other users' requests, poisoning request queues, cache poisoning, and in some configurations, remote code execution. James Kettle's research at PortSwigger (published around 2019–2020) demonstrated that request smuggling could be used to steal session tokens, bypass WAFs, and perform cross-site request forgery at scale against users of the same application.

The root cause is that HTTP/1.1 provides two mechanisms for specifying the length of a request body: Content-Length (CL) and Transfer-Encoding: chunked (TE). When a front-end and back-end server use different mechanisms to determine where a request ends, ambiguity arises. RFC 7230 specifies that if both headers are present, Transfer-Encoding takes precedence—but not all servers implement this correctly.

HTTP Body Length: Content-Length vs Transfer-Encoding

Understanding the vulnerability requires understanding how these two mechanisms work.

Content-Length: The Content-Length header specifies the exact size of the request body in bytes. The receiving server reads exactly that many bytes as the body and then treats the next byte as the start of a new request.

POST /search HTTP/1.1
Host: example.com
Content-Length: 11

hello world

Transfer-Encoding: chunked: The body is sent in chunks, each preceded by the chunk size in hexadecimal. A zero-length chunk signals the end of the body.

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

b
hello world
0

(The 0\r\n\r\n terminates the chunked body.)

When both headers are present on the same request, RFC 7230 says Transfer-Encoding takes precedence and Content-Length must be ignored. The attack exploits servers that do not follow this specification.

CL.TE: Content-Length Front-End, Transfer-Encoding Back-End

In a CL.TE attack:

  • The front-end server uses Content-Length to determine request boundaries
  • The back-end server uses Transfer-Encoding to determine request boundaries

The attacker crafts a request that contains both headers. The front-end reads exactly Content-Length bytes and forwards the entire thing to the back-end. The back-end uses chunked encoding, so it reads the chunk body—but the "zero-chunk" terminator comes before Content-Length bytes have been consumed. The remaining bytes after the zero-chunk are left in the TCP buffer on the back-end, where they will be prepended to the next request in the queue.

Attack example:

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

0

G
  • Front-end (CL): Sees Content-Length: 6, forwards 6 bytes of body (0\r\n\r\nG) to the back-end
  • Back-end (TE): Sees chunked encoding. Reads chunk 0 (zero-length), terminates. The remaining G is left in the buffer as the start of the next request

The next legitimate user's request arrives at the back-end prefixed with G—turning their GET /dashboard into GGET /dashboard, which the server may handle differently, or an attacker can craft something more targeted.

More impactful CL.TE attack—stealing another user's request:

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

0

GET /admin HTTP/1.1
Host: vulnerable.example.com
X-Forwarded-For: 127.0.0.1
Foo: x

The front-end forwards the full 42 bytes. The back-end processes up to the zero-chunk. The back-end's buffer now contains:

GET /admin HTTP/1.1
Host: vulnerable.example.com
X-Forwarded-For: 127.0.0.1
Foo: x

The next user's request is appended to this, completing the smuggled request. The back-end processes GET /admin with the X-Forwarded-For: 127.0.0.1 header—potentially bypassing IP-based access controls that restrict the admin panel to localhost.

TE.CL: Transfer-Encoding Front-End, Content-Length Back-End

In a TE.CL attack, the roles are reversed:

  • The front-end server uses Transfer-Encoding: chunked
  • The back-end server uses Content-Length
POST / HTTP/1.1
Host: vulnerable.example.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

  • Front-end (TE): Reads chunk 8 (8 bytes: SMUGGLED), then chunk 0 (terminator). Forwards the complete request including the chunked encoding.
  • Back-end (CL): Sees Content-Length: 3, reads only 3 bytes (8\r\n). The rest (SMUGGLED\r\n0\r\n\r\n) remains in the buffer.

Again, the buffer content is prepended to the next request.

TE.TE: Obfuscated Transfer-Encoding

A more subtle variant: both front-end and back-end process Transfer-Encoding, but one can be induced to ignore it via header obfuscation:

Transfer-Encoding: chunked
Transfer-Encoding: identity

or:

Transfer-Encoding: xchunked
Transfer-Encoding: chunked

or using Unicode characters, whitespace, or unusual formatting in the Transfer-Encoding header value. If the front-end's parser recognizes the obfuscated form but the back-end does not (or vice versa), the effective behavior reduces to CL.TE or TE.CL.

HTTP/2 and Desync

HTTP/2 eliminated the Content-Length vs Transfer-Encoding ambiguity by using a binary protocol with explicit framing. However, many deployments use HTTP/2 between clients and the front-end but HTTP/1.1 between the front-end and back-end (H2.CL or H2.TE attacks). When the front-end downgrades HTTP/2 requests to HTTP/1.1 for the back-end, it may add or forward Content-Length and Transfer-Encoding headers in ways that create the same ambiguity.

Why Request Smuggling Is Particularly Dangerous

  1. It affects all users, not just the attacker: The smuggled prefix is injected into the request of whichever legitimate user makes the next request. This enables mass exploitation.

  2. It bypasses IP and authentication controls: The smuggled request can carry forged headers (X-Forwarded-For: 127.0.0.1) that bypass controls applied based on client IP.

  3. It evades WAFs: A WAF inspecting individual requests may see the outer request as benign. The malicious inner request is interpreted only by the back-end.

  4. Request capture: An attacker can craft a smuggled request that ends with a truncated HTTP header. The next user's full request—including their cookies and session tokens—is appended to complete the header, captured by the back-end and potentially returned to the attacker.

  5. Web cache poisoning: If the response to the smuggled request is cached, the poisoned cache entry serves the malicious response to subsequent users.

Detection with Burp Suite

Burp Suite's HTTP Request Smuggler extension (by James Kettle) automates detection. Install it via the BApp Store.

Manual detection—CL.TE probe:

Send two requests in rapid succession to the same connection:

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

0

GET /404notfound HTTP/1.1
X-Foo: x

Follow immediately with a normal request. If the normal request receives a 404 response (the response to GET /404notfound), the server is vulnerable to CL.TE.

Time-delay probe:

A safe detection technique that does not interfere with other users:

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

1
A
X

If the back-end uses Content-Length: 4, it reads 1\r\n (4 bytes) and waits for the rest. The connection hangs, timing out after the server's read timeout. A delayed response (~10 seconds) confirms TE.CL.

Burp's Active Scan now includes request smuggling probes. Run an active scan against your staging environment to detect the most common variants automatically.

Prevention

Normalize Requests at the Front-End

Configure your reverse proxy/load balancer to normalize ambiguous requests before forwarding them:

  • Reject any request with both Content-Length and Transfer-Encoding headers
  • Use only Transfer-Encoding: chunked internally (strip Content-Length from forwarded requests)
  • Reject requests with ambiguous or obfuscated Transfer-Encoding values

Nginx configuration (reject CL+TE combination):

# Disable keepalive connections if smuggling is a concern
# (prevents connection reuse that makes smuggling possible)
upstream backend {
    server backend:8080;
    keepalive 0;  # Disable keepalive
}

# Or strictly enforce one Transfer-Encoding format
if ($http_transfer_encoding ~* "chunked" ) {
    # Process chunked; reject if Content-Length also present
    # Use Nginx limit_req and custom logic
}

HAProxy (reject invalid Transfer-Encoding):

frontend http_front
    # Reject requests with both CL and TE headers
    http-request deny if { req.fhdr(transfer-encoding) -i chunked } { req.fhdr(content-length) -i [0-9] }

Use HTTP/2 End-to-End

HTTP/2 between client, front-end, and back-end eliminates the CL/TE ambiguity entirely. If your infrastructure supports it, enable HTTP/2 on internal connections (not just client-facing).

Many modern service meshes (Istio, Linkerd) use HTTP/2 or gRPC internally, which sidesteps this class of vulnerability.

Ensure Consistent HTTP Parsing

Ensure your front-end and back-end servers use the same HTTP parsing library with consistent behavior. Heterogeneous stacks (nginx in front of a Java Tomcat back-end with different HTTP libraries) are higher risk than homogeneous stacks.

Reject Ambiguous Requests

The most conservative defense: reject any request that contains both Content-Length and Transfer-Encoding headers with a 400 Bad Request. RFC 7230 permits this behavior:

If any transfer coding other than chunked is applied to a request payload body, the sender MUST apply chunked as the final transfer coding to ensure that the message is properly framed. If any transfer coding other than chunked is applied to a request payload body, the sender MUST apply chunked as the final transfer coding...

A server that receives a request message with a transfer coding it does not understand SHOULD respond with 501 (Not Implemented).

Disable Back-End Connection Reuse

Request smuggling requires that the smuggled prefix and the victim's request share the same back-end TCP connection. Disabling connection reuse (HTTP persistent connections / keepalive) between front-end and back-end eliminates this. The performance cost is significant—a new TCP connection per request—but it is a complete mitigation for CL.TE and TE.CL smuggling.

This is not recommended as a permanent solution but is an emergency mitigation if active exploitation is suspected.

Web Application Firewall Rules

WAFs can detect known smuggling payloads, particularly requests with both CL and TE headers or obfuscated TE values. Cloudflare and AWS WAF both have managed rules that target request smuggling patterns.

Request smuggling is a reminder that protocol implementation details matter for security. The HTTP/1.1 specification's ambiguity around transfer encoding created a vulnerability class that has persisted for over a decade. Moving to HTTP/2 universally is the permanent fix; careful proxy configuration is the pragmatic interim solution.

HTTP-request-smuggling
web-security
proxy
Burp-Suite
network-security
HTTP

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.