Editorial
Una web de editorial permite subir una portada indicando una URL. Esa función hace peticiones desde el servidor (SSRF) y llega a una API interna que filtra credenciales. Con ellas se entra por SSH, en un repositorio git olvidado aparece la contraseña de otro usuario, y ese usuario puede ejecutar con sudo un script vulnerable.
- Acceso inicialSSRF a la API interna
- Movimiento lateralCredenciales en git log
- EscaladaGitPython vulnerable con sudo
Reconocimiento
nmap -p- --min-rate 5000 -sS -oN puertos.txt <IP>
nmap -sCV -p22,80 -oN servicios.txt <IP>| Puerto | Servicio |
|---|---|
| 22 | SSH |
| 80 | HTTP (nginx) |
El puerto 80 redirige a un nombre de dominio, así que hay que añadirlo a /etc/hosts:
echo "<IP> editorial.htb" | sudo tee -a /etc/hosts
whatweb http://editorial.htbAcceso inicial
1. Función de subida con SSRF
La web tiene un formulario para publicar un libro con portada. La portada se puede dar como archivo o como URL. Si es una URL, el servidor la descarga por nosotros: eso es lo que queremos.
Con Burp Suite capturo la petición de "vista previa", que va a /upload-cover, y pruebo que el servidor me hace peticiones a mi propia máquina (levanto python3 -m http.server y apunto la URL a mi IP). Llega la petición, así que hay SSRF.
2. Buscar servicios internos
Lo que devuelve el servidor es una ruta de imagen, no el contenido. Eso significa que solo veo si "funcionó" o no. Pruebo puertos locales (http://127.0.0.1:PUERTO) con Intruder hasta ver que el 5000 responde distinto.
Al pedir http://127.0.0.1:5000, el servidor guarda la respuesta como si fuera la portada. Descargando ese archivo veo un JSON con la lista de endpoints de una API interna. Los voy consultando uno a uno por el mismo SSRF, y /api/latest/metadata/messages/authors devuelve un mensaje de bienvenida para nuevos autores con usuario y contraseña del usuario dev.
3. SSH
ssh dev@editorial.htbCon esas credenciales entro por SSH y ya puedo leer la primera flag del usuario.
Movimiento lateral: git
En el directorio personal hay una carpeta apps que contiene un .git. Los repositorios guardan el historial, y a veces se sube algo sensible y luego se borra, pero el commit antiguo sigue ahí.
cd ~/apps
git log
git show <hash-del-commit-sospechoso>El commit change(api): downgrading prod to dev cambió el mensaje de bienvenida de la API. Con git show se ve la versión anterior, que tenía las credenciales del usuario prod. Entro con ellas:
su prodEscalada de privilegios
sudo -lEste usuario puede ejecutar como root /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py con cualquier argumento. El script clona el repositorio que le pase usando la librería GitPython, y la versión instalada (3.1.29) es vulnerable a CVE-2022-24439: si la URL usa el transporte ext::, git ejecuta el comando que va detrás.
pip3 list | grep -i gitpython # confirmar la versión
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c chmod% u+s% /bin/bash'
bash -pEn la URL, % hace de espacio. El comando no devuelve salida, pero se ejecuta como root: aquí le pongo el bit SUID a /bin/bash, y bash -p me da una shell que conserva esos privilegios (ver SUID).
Lo que me llevo
- Una función "descargar desde URL" casi siempre es candidata a SSRF.
- El SSRF no sirve solo para leer archivos: sirve para llegar a servicios que solo escuchan en
localhost. - Los repositorios
.gitguardan el pasado. Siempregit logantes de seguir. - Mirar
sudo -les de lo primero que hay que hacer tras entrar.
Cómo se arregla
- Validar y restringir las URL que acepta el servidor (lista blanca de dominios, bloquear rangos internos como
127.0.0.0/8). - No poner credenciales en mensajes ni en commits; si ocurre, rotarlas.
- Actualizar GitPython y no permitir que un usuario ejecute con
sudoscripts que aceptan entradas arbitrarias.
Referencias
- Documentación de SSRF (PortSwigger)
- CVE-2022-24439 en GitPython (issue original)
- Técnica relacionada: SSRF
