SSRF sin Complicaciones: Explotando Cloud Metadata en Lab AWS Local
Reproduccion etica de SSRF contra IMDS con LocalStack, payloads reales, captura de credenciales simuladas y mitigacion definitiva con IMDSv2.
El endpoint 169.254.169.254 ha quemado mas carreras de SRE que cualquier otra IP en la historia de la nube. Capital One en 2019 perdio 100 millones de registros porque un WAF mal configurado permitio que un SSRF llegara al Instance Metadata Service y descargara credenciales temporales de la role de EC2. Siete anos despues seguimos viendo reportes de bug bounty con el mismo patron: la app descarga una imagen desde una URL provista por el usuario, nadie valida el destino, IMDSv1 sigue activo, fin de juego. Reproduzcamos el ataque desde cero en un lab AWS local con LocalStack, entendamos por que IMDSv2 cambia las reglas y cerremos con un checklist de hardening accionable hoy.
Primero el entorno. Levanta LocalStack Pro 4.x via docker-compose con servicios ec2, iam, sts y s3 habilitados, mas un contenedor Flask en el puerto 5000 corriendo una API vulnerable de fetch-image. El codigo tiene dieciocho lineas: recibe ?url=, llama a requests.get sin validacion, devuelve el body. Para simular el IMDS dentro de LocalStack necesitas el plugin localstack-ec2-metadata-mock o levantar un mock dedicado en 169.254.169.254 via netns. Quien prefiere maxima fidelidad usa un lab EC2 t3.micro real por dos centavos la hora, pero LocalStack cubre el 95% del aprendizaje sin tarjeta de credito. Pentest Web desde Cero: Montando un Lab Seguro con DVWA, Juice Shop y Burp Suite cubre el setup base de contenedores vulnerables que reutilizas aqui.
Con el lab activo, el payload clasico de IMDSv1 es literalmente un GET. Desde Burp intercepta la peticion de la app y cambia el parametro url a http://169.254.169.254/latest/meta-data/iam/security-credentials/. La respuesta entrega el nombre de la role asociada, digamos app-server-role. Luego GET http://169.254.169.254/latest/meta-data/iam/security-credentials/app-server-role devuelve un JSON con AccessKeyId, SecretAccessKey y Token. Exportalos como variables de entorno, ejecuta aws sts get-caller-identity --endpoint-url http://localhost:4566 y estas autenticado como la aplicacion. Ese es el momento donde los blue teams saltan de la silla en la demo.
La escalada depende de los permisos de la role. En nuestro lab adjunte una policy deliberadamente permisiva con s3:* y iam:ListRoles. Con las credenciales robadas: aws s3 ls revela un bucket backups-prod-2026, aws s3 cp s3://backups-prod-2026/db.dump baja el dump y aws iam list-attached-role-policies mapea el camino para escalada via PassRole. Herramientas como Pacu, ScoutSuite y cloudfox automatizan esta enumeracion post-compromiso. Patrones similares aparecen en Pentest de APIs REST y GraphQL: Checklist Tecnico para Bug Bounty Legal cuando endpoints webhook o image-proxy aceptan URLs internas sin allowlist. Documenta cada comando con timestamp porque un reporte de bug bounty sin PoC reproducible paga cero.
IMDSv2 rompe el ataque exigiendo una sesion con token. El flujo correcto es PUT http://169.254.169.254/latest/api/token con header X-aws-ec2-metadata-token-ttl-seconds: 21600 y luego GET con header X-aws-ec2-metadata-token. Un SSRF que solo hace GET, sin control de headers ni metodo, simplemente se traba. Para forzar IMDSv2 usa aws ec2 modify-instance-metadata-options --http-tokens required --http-put-response-hop-limit 1. El hop-limit 1 es crucial: bloquea contenedores en red bridge de alcanzar el IMDS via NAT del host. Combina con bloqueo de salida 169.254.0.0/16 en el security group y la app pierde cualquier ruta al endpoint magico.
La defensa en capas no termina en el IMDS. Valida URLs en la app rechazando 169.254.0.0/16, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8 y resuelve el hostname antes de la peticion para evitar DNS rebinding. Usa una libreria como ssrf-protect o implementalo con getaddrinfo mas verificacion por familia de IP. Reduce permisos de role al minimo necesario, prefiere IRSA en EKS, activa GuardDuty que detecta uso anomalo de credenciales, y ejecuta scanners como Prowler con regularidad. Tecnicas web correlacionadas aparecen en SQL Injection en la Practica: Explotar, Detectar y Mitigar en Lab Controlado y XSS Moderno: DOM, Stored y Reflected con Ejemplos Reales en Entorno de Pruebas, que junto con SSRF forman el trio mas comun en bug bounty corporativo. Quien quiera profundizar en pivoting interno tras obtener credenciales encontrara Pivoting con Chisel y Ligolo-ng: Redes Segmentadas en Lab de Pentest como siguiente paso.
Takeaway practico: corre aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens]' sobre tu inventario AHORA. Cualquier instancia que devuelva optional es vulnerable al SSRF clasico. Forza required masivamente via SSM Automation Document, audita roles con policies *:* y configura un test E2E en CI que intente acceder a 169.254.169.254 desde el contenedor de la app y rompa el build si recibe respuesta 200. Tarde diecisiete minutos en reproducir Capital One en el laboratorio. Toma el mismo tiempo cerrar el hueco en produccion.