SSRF:
cómo una petición del servidor acaba leyendo las credenciales de tu nube
Seguridad de aplicaciones web / Cloud | unfantasmaenelsistema.com
Casi todas las aplicaciones web hacen, en algún momento, una petición de red por su cuenta: descargar la imagen de un avatar a partir de una URL que pega el usuario, comprobar un webhook, renderizar la vista previa de un enlace, importar un feed, convertir un documento remoto a PDF. Desde fuera parece inofensivo: el usuario da una dirección y el servidor va a buscarla. El problema es quién hace esa petición y desde dónde. El servidor está dentro de tu red, tiene acceso a servicios internos que no miran a internet y, si vive en la nube, lleva colgando unas credenciales que valen oro.
Eso es SSRF (Server-Side Request Forgery, falsificación de petición del lado del servidor): conseguir que el servidor haga una petición que el atacante elige, a un destino que el atacante elige. Entró en el OWASP Top 10 en la edición de 2021 como categoría propia (A10) precisamente porque dejó de ser una curiosidad para convertirse en una de las vías más rentables hacia los datos. El caso más conocido, la brecha de Capital One en 2019, expuso datos de más de 100 millones de personas y empezó exactamente así. En este artículo vemos cómo funciona, por qué el endpoint de metadatos de la nube es un imán, cómo se saltan los filtros ingenuos y cómo se defiende en serio.
Qué aprenderás en este artículo
Qué es un SSRF y por qué el servidor es un origen de petición privilegiado · El endpoint de metadatos 169.254.169.254 y la diferencia entre IMDSv1 e IMDSv2 · Otros destinos internos jugosos: Redis, la API de Kubernetes, paneles de administración · Cómo los filtros por lista negra se saltan con redirecciones, DNS rebinding y representaciones raras de IP · Defensa en capas: allowlist de destinos, bloqueo de rangos internos, egress filtering y forzar IMDSv2
1. Qué es exactamente un SSRF
Un SSRF aparece siempre que la aplicación toma una entrada del usuario y la usa —entera o en parte— para construir la dirección de una petición de red que hace el propio servidor. La entrada no tiene por qué ser una URL evidente en un campo de texto: puede ser una cabecera, un parámetro que parece un nombre de host, un campo dentro de un XML o un JSON, o el valor de un webhook configurable.
La gravedad depende de qué puede alcanzar el servidor y de cuánta información devuelve al atacante. Se distinguen dos variantes:
- SSRF con respuesta (in-band): el contenido de la petición interna se refleja de vuelta al atacante —en el cuerpo de la respuesta, en un mensaje de error, en la vista previa generada—. Es el escenario más directo: se lee lo que devuelva el servicio interno.
- SSRF ciego (blind): el servidor hace la petición pero no devuelve el contenido. Aún es explotable midiendo tiempos, provocando errores distintos según el destino, o apuntando a servicios que ejecutan una acción con solo recibir la petición (un endpoint que reinicia algo, que encola una tarea, etc.).
En ambos casos el atacante ha convertido tu servidor en un proxy que habla desde dentro de tu perímetro. Todo lo que el cortafuegos perimetral bloquea desde fuera —la consola de administración, la base de datos, el orquestador— ahora es alcanzable a través de la aplicación.
2. Por qué el servidor es un origen de petición privilegiado
Cuando tú visitas una web, tu navegador sale a internet desde una red doméstica o corporativa sin nada especial detrás. Cuando la petición la hace un servidor de aplicación, ese servidor suele estar en una posición de red mucho más sensible:
- Comparte segmento con bases de datos, colas de mensajes, cachés y APIs internas que no exigen autenticación porque «solo se llega desde dentro».
- Puede resolver nombres DNS internos y llegar a paneles de administración publicados solo en la red privada.
- Si corre en un proveedor de nube (AWS, Google Cloud, Azure, Oracle Cloud), tiene acceso a un servicio de metadatos local que entrega configuración de la instancia y, muy a menudo, credenciales temporales del rol asociado a esa máquina.
- Tiene acceso a la red de contenedores: la API del kubelet, el socket de Docker si está mal expuesto, el servidor de la API de Kubernetes.
El atacante no necesita entrar en el servidor. Le basta con dictarle a dónde llamar y leer la respuesta.
3. El endpoint de metadatos: 169.254.169.254
Todos los grandes proveedores de nube exponen un Instance Metadata Service (IMDS) en la dirección de enlace local 169.254.169.254. Es accesible solo desde la propia máquina, sin autenticación, y sirve para que la instancia se autoconfigure: nombre, región, claves SSH públicas, scripts de arranque (user-data) y, la joya, las credenciales del rol IAM que tenga adjuntado.
Esas credenciales son un juego de clave de acceso, clave secreta y token de sesión con la vida corta (minutos u horas) que el SDK de la nube renueva solo. Con ellas, un atacante hereda todos los permisos del rol de la instancia: si ese rol puede leer buckets de almacenamiento, el atacante lee buckets; si puede lanzar máquinas o leer secretos, también. Aquí es donde un SSRF de «descárgame esta imagen» se transforma en una brecha de datos a gran escala.
3.1 IMDSv1 frente a IMDSv2
La versión original del servicio (IMDSv1) responde a una simple petición GET. Eso la hace trivial de alcanzar por SSRF: si el atacante logra que el servidor haga un GET a una URL que él controla, puede hacer que lo haga contra el endpoint de metadatos. La versión endurecida (IMDSv2) obliga a un flujo en dos pasos y añade defensas pensadas específicamente contra SSRF.
| Aspecto | IMDSv1 | IMDSv2 |
|---|---|---|
| Cómo se pide | Un solo GET a la URL de metadatos | Primero un PUT para obtener un token de sesión, luego GET con ese token en una cabecera |
| Resistencia a SSRF | Baja: casi cualquier SSRF de GET la alcanza | Alta: la mayoría de SSRF no pueden emitir un PUT ni añadir cabeceras arbitrarias |
| Límite de saltos de red (hop limit) | No aplica | El paquete de respuesta tiene TTL configurable (por defecto 1 o 2): no puede salir de la instancia ni llegar a un contenedor a través de un puente |
| Cabecera de origen | No se comprueba | Se rechazan peticiones que traen cabeceras típicas de proxy (X-Forwarded-For), señal de que vienen reenviadas |
| Estado recomendado | Deshabilitada | Obligatoria (http-tokens: required) |
Forzar IMDSv2 no elimina el riesgo por completo —un SSRF muy potente que permita controlar el método y las cabeceras aún podría llegar—, pero corta de raíz la inmensa mayoría de exploits reales, que se basan en un GET simple. Es la mitigación con mejor relación coste/efecto de toda esta lista.
4. Más allá de la nube: otros destinos internos
El endpoint de metadatos es el objetivo estrella, pero un SSRF sirve para hablar con cualquier cosa que el servidor pueda alcanzar. Algunos destinos habituales:
| Destino interno | Qué se busca | Por qué es alcanzable |
|---|---|---|
| Servicio de metadatos (169.254.169.254) | Credenciales del rol, user-data, claves | Sin autenticación, pensado para acceso local |
| Redis / Memcached (6379 / 11211) | Leer o escribir datos de sesión y caché; en Redis, a veces ejecución de comandos | Protocolo de texto sin autenticación por defecto, tolerante a basura al principio de la conexión |
| API de Kubernetes / kubelet (6443 / 10250) | Enumerar pods y secretos, ejecutar en contenedores | Publicada en la red del clúster; el kubelet histórico permitía acceso anónimo |
| Bases de datos internas | Volcado de datos, a veces vía funciones de la propia base | En la misma VPC/segmento, filtradas solo por red |
| Paneles de administración internos | Jenkins, consolas de CI/CD, dashboards sin login desde la red interna | Publicados «solo en la intranet» y sin autenticación fuerte |
| El propio localhost del servidor | Endpoints de gestión (actuator, /debug, métricas) que exponen configuración y secretos | Escuchan en 127.0.0.1 confiando en que nadie de fuera llega |
Un detalle importante: muchos de estos servicios hablan protocolos que no son HTTP, pero SSRF con esquemas como gopher://, dict:// o file:// (si la biblioteca cliente los admite) permite enviar bytes casi arbitrarios a un puerto, lo que abre la puerta a interactuar con Redis o SMTP. Restringir los esquemas permitidos a http y https es parte de la defensa.
5. El caso Capital One (2019)
Es el ejemplo canónico y merece contarse porque encadena todos los eslabones de este artículo. Un WAF mal configurado (un proxy inverso de la propia infraestructura) era vulnerable a SSRF. A través de él se alcanzó el endpoint de metadatos 169.254.169.254 —IMDSv1— y se obtuvieron las credenciales temporales del rol asignado a esa máquina. Ese rol tenía permiso para listar y leer buckets de almacenamiento. Con las credenciales en la mano, se enumeraron los buckets y se descargaron unos 100 GB de datos con información de más de 100 millones de solicitantes de tarjetas.
Las lecciones se leen hacia atrás en la cadena: el rol de la instancia tenía más permisos de los necesarios (violación de mínimo privilegio); el servicio de metadatos era la versión insegura; y no había egress filtering que impidiera a esa máquina hablar con 169.254.169.254 o sacar gigabytes hacia fuera sin que saltara nada. Cualquiera de esas tres barreras habría contenido el incidente.
6. Por qué las listas negras no funcionan
La defensa ingenua es «si la URL apunta a 127.0.0.1 o a 169.254.169.254, la rechazo». No sirve, porque hay demasiadas formas de escribir la misma dirección y demasiados momentos en los que comprobarla. Las técnicas clásicas de evasión:
| Técnica | Idea | Ejemplo ilustrativo |
|---|---|---|
| Representaciones alternativas de IP | La misma dirección escrita en decimal, octal, hexadecimal o con ceros | 2130706433, 0177.0.0.1, 0x7f.0.0.1 equivalen a 127.0.0.1 |
| Direcciones que «parecen» externas | Dominios públicos que resuelven a IP internas, o el rango 169.254.x que no siempre se filtra | Un nombre propio apuntando a 169.254.169.254 |
| Redirecciones abiertas | La URL inicial es externa y legítima; el servidor la sigue y termina en un 302 hacia el destino interno | https://sitio-legitimo/redirect?to=http://169.254.169.254/… |
| DNS rebinding (TOCTOU) | El nombre resuelve a una IP pública cuando se valida y a una interna un instante después, cuando se conecta | Registro DNS con TTL 0 que alterna entre dos IP |
| Confusión de parser de URL | Validador y cliente HTTP interpretan de forma distinta credenciales, arrobas o fragmentos en la URL | http://esperado@169.254.169.254/ o http://169.254.169.254#.esperado/ |
| IPv6 y direcciones mapeadas | Notación IPv6 de loopback o de IPv4 embebida en IPv6 | [::1], [::ffff:169.254.169.254] |
El patrón común es que validar una cadena de texto no es lo mismo que controlar a qué IP se conecta de verdad el socket. La defensa robusta actúa en el momento de la conexión, sobre la IP ya resuelta, y vuelve a comprobarla tras cada redirección.
7. Defensa en capas
Ningún control aislado cierra el SSRF. La combinación sí. De la aplicación hacia la infraestructura:
| Capa | Control | Qué aporta |
|---|---|---|
| Aplicación | Allowlist de destinos: solo dominios/hosts concretos que la función necesita de verdad | Convierte el problema en «solo puede ir a donde le decimos», que es defendible |
| Aplicación | Validar sobre la IP resuelta, no sobre la cadena; rechazar rangos privados, loopback, link-local (169.254.0.0/16), multicast y metadatos | Neutraliza las representaciones raras de IP y los dominios que resuelven a interno |
| Aplicación | Prohibir esquemas distintos de http/https; no seguir redirecciones automáticamente, o revalidar el destino en cada salto | Cierra gopher/dict/file y las redirecciones abiertas |
| Aplicación | Cliente HTTP dedicado para peticiones «salientes por usuario», con timeouts cortos, sin credenciales y sin cookies del dominio | Reduce el valor de una petición forzada aunque llegue a destino |
| Nube | Forzar IMDSv2 (http-tokens: required) y hop limit 1; desactivar el servicio de metadatos donde no haga falta | Elimina el exploit de GET simple contra 169.254.169.254 |
| Nube / IAM | Mínimo privilegio real en el rol de la instancia; nada de comodines | Aunque se filtren las credenciales, el alcance es pequeño |
| Red | Egress filtering: la máquina de aplicación solo sale a los destinos que necesita; bloquear 169.254.169.254 desde procesos que no sean el agente de la nube | Contiene tanto la llegada al metadata como la exfiltración posterior |
| Detección | Alertar sobre peticiones a rangos internos desde la app, uso de credenciales de rol desde IP ajenas a la instancia, picos de tráfico saliente | Detecta el intento y el uso posterior de las credenciales |
Sobre esa última fila: los proveedores de nube registran de dónde se usa cada credencial. Una credencial de rol de instancia que de pronto se utiliza desde una IP que no es la de esa instancia es una señal muy fuerte de robo, y se puede alertar sobre ella.
7.1 Comprobar tu propia configuración
Comandos de lectura y auditoría sobre infraestructura propia, para verificar el estado de IMDS y cómo responde una función que hace peticiones salientes. No son pasos de intrusión: se ejecutan contra tus propias instancias.
# --- Ver el modo de metadatos de tus instancias EC2 (solo lectura) --- aws ec2 describe-instances \ --query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpPutResponseHopLimit]' \ --output table # --- Forzar IMDSv2 y hop limit 1 en una instancia propia --- aws ec2 modify-instance-metadata-options \ --instance-id i-0123456789abcdef0 \ --http-tokens required \ --http-put-response-hop-limit 1 \ --http-endpoint enabled # --- Desde DENTRO de tu instancia: con IMDSv2 un GET pelado ya no basta --- curl -s http://169.254.169.254/latest/meta-data/ # -> 401 si v2 es obligatorio TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 60") curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/
Y un esqueleto defensivo del lado de la aplicación: resolver el host, comprobar que la IP no cae en un rango prohibido y solo entonces conectar. En producción conviene apoyarse en una biblioteca mantenida específica de anti-SSRF en lugar de mantener esta lógica a mano.
import ipaddress, socket
from urllib.parse import urlparse
BLOQUEADOS = [
ipaddress.ip_network('127.0.0.0/8'), ipaddress.ip_network('10.0.0.0/8'),
ipaddress.ip_network('172.16.0.0/12'), ipaddress.ip_network('192.168.0.0/16'),
ipaddress.ip_network('169.254.0.0/16'), # link-local + metadatos
ipaddress.ip_network('::1/128'), ipaddress.ip_network('fc00::/7'),
]
ESQUEMAS_OK = {'http', 'https'}
def destino_permitido(url: str) -> bool:
p = urlparse(url)
if p.scheme not in ESQUEMAS_OK or not p.hostname:
return False
# resolver TODAS las IP del host y rechazar si alguna es interna
for fam, _, _, _, sockaddr in socket.getaddrinfo(p.hostname, p.port or 80):
ip = ipaddress.ip_address(sockaddr[0])
if any(ip in red for red in BLOQUEADOS) or not ip.is_global:
return False
return True
# Nota: para cerrar el DNS rebinding hay que CONECTAR a la IP ya validada
# (no volver a resolver el nombre) y revalidar tras cada redireccion.Marco legal: pruébalo solo contra lo que es tuyo
Provocar peticiones SSRF, alcanzar servicios de metadatos o servicios internos y usar credenciales obtenidas así contra sistemas de terceros es delito en España: acceso ilícito a sistemas de información (art. 197 bis del Código Penal), facilitación de herramientas para ello (art. 197 ter) y daños informáticos (art. 264) · Las pruebas se hacen sobre infraestructura propia o con autorización escrita y por escrito del titular (un contrato de pentest con alcance definido) · Para practicar: aplicaciones deliberadamente vulnerables en un laboratorio aislado (OWASP Juice Shop, WebGoat, contenedores propios en el rango 192.168.56.x de la serie), nunca servicios reales ni el metadata de una cuenta de nube que no sea tuya
Al revisar tu propio código, busca estos patrones
Cualquier función que reciba una URL o un host del usuario y llame a requests / fetch / curl / http.Client con ese valor · Vistas previa de enlaces, importadores de imágenes/feeds, conversores de documentos, comprobadores de webhooks, integraciones con «pon aquí la URL de tu servidor» · Analizadores de XML con entidades externas habilitadas (XXE), que son una vía de SSRF por sí mismos · Clientes HTTP que siguen redirecciones por defecto sin revalidar el destino
Conclusión
El SSRF es un fallo de confianza mal colocada: la aplicación asume que una petición «la hace el servidor, así que es de fiar», cuando en realidad el destino lo elige quien envía la entrada. Y el servidor está en el peor sitio posible para que eso ocurra —dentro del perímetro, con acceso a servicios sin autenticar y, en la nube, con credenciales colgando de un endpoint sin contraseña en 169.254.169.254—.
La buena noticia es que se defiende bien y con controles baratos: una allowlist de destinos, validación sobre la IP resuelta y no sobre el texto, forzar IMDSv2, aplicar mínimo privilegio al rol de la instancia y filtrar el tráfico saliente. Ninguno es difícil; el problema histórico ha sido no aplicar ninguno. El caso Capital One es la demostración de lo que cuesta esa omisión.
Referencias y recursos
OWASP Top 10:2021 A10 Server-Side Request Forgery — owasp.org · OWASP SSRF Prevention Cheat Sheet — cheatsheetseries.owasp.org · AWS: «Add defense in depth against open firewalls, reverse proxies, and SSRF» y la guía de transición a IMDSv2 · PortSwigger Web Security Academy — laboratorios gratuitos de SSRF para practicar en su entorno · Nuestros artículos de OWASP Top 10:2025, seguridad de APIs REST y el próximo sobre AWS IAM
unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)

