En la lección anterior vimos que el Top Ten 2021 integró el antiguo XXE dentro de A05 (Configuración de Seguridad Incorrecta), porque en el fondo un XXE es un parser XML mal configurado. Ahora abrimos esa caja con detalle, porque tiene técnica, explotación y remediación propias. El XXE (XML External Entities) aprovecha una característica del estándar XML —las entidades externas, definidas en la DTD— que muchos parsers procesan por defecto, para hacer que el servidor lea ficheros locales, realice peticiones de red en nombre del atacante (SSRF, que veremos en A10) o se sature (DoS).
El escenario perfecto para BazarNube es el módulo legacy Java/Spring de facturación y pedidos, que recibe documentos XML de proveedores y sistemas externos. Un XML de factura manipulado puede convertirse en una herramienta para leer /etc/passwd, alcanzar servicios internos o tumbar el servicio. Este es uno de los focos clásicos de código Java antiguo.
Aviso legal y ético: los
payloadsXML son ilustrativos y con datos ficticios. Practica solo sobre sistemas propios o con autorización explícita. Leer ficheros del servidor o alcanzar su red interna sin permiso es delito.
Contenido
- Qué es una entidad XML y por qué es peligrosa
- XXE de lectura de ficheros en el módulo Java de BazarNube
- SSRF a través de XXE
- DoS: el ataque "billion laughs"
- Endurecer parsers XML en Java
- Endurecer parsers XML en Node
- Alternativas y defensa en profundidad
- Errores comunes, ejercicios y soluciones
- Qué es una entidad XML
XML permite definir entidades, que son como variables. Una entidad interna es texto; una entidad externa apunta a un recurso fuera del documento mediante SYSTEM y una URI (file://, http://). Cuando el parser expande esa entidad, va a buscar el recurso. Ese comportamiento, unido a datos controlados por el atacante, es la esencia del XXE.
<!-- Entidad externa: al expandirse, el parser LEE el fichero indicado -->
<!DOCTYPE factura [
<!ENTITY secreto SYSTEM "file:///etc/passwd">
]>
<factura>&secreto;</factura>Si el parser procesa la DTD y expande entidades externas, el contenido de /etc/passwd acaba dentro del documento y, a menudo, reflejado en la respuesta.
- XXE de lectura de ficheros en BazarNube
El módulo de facturación parsea el XML entrante con la configuración por defecto de DocumentBuilderFactory:
// InvoiceParser.java (modulo legacy Java) — VULNERABLE a XXE
public Document parse(InputStream xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// Por defecto NO deshabilita DTD ni entidades externas
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(xml); // procesa entidades externas del atacante
}Cómo se explota (ilustrativo)
Un proveedor malicioso (o alguien que intercepta el flujo) envía esta "factura":
<?xml version="1.0"?>
<!DOCTYPE factura [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<factura>
<cliente>&xxe;</cliente>
<total>0</total>
</factura>Al parsear, el motor expande &xxe; leyendo /etc/passwd. Si BazarNube devuelve luego el campo cliente en algún mensaje o log accesible, el atacante obtiene el fichero. Con variantes puede leer código fuente, ficheros de configuración con credenciales o claves.
Corrección: deshabilitar DTD y entidades externas
La forma más robusta en Java es prohibir por completo las DTD (disallow-doctype-decl). Si el flujo no necesita DTD (casi nunca lo necesita), esto cierra XXE de raíz:
// InvoiceParser.java — SEGURO
public Document parse(InputStream xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 1) Prohibir DTD por completo: bloquea XXE y billion laughs de golpe
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 2) Refuerzo por si algun flujo necesitara DTD parcial:
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(xml);
}Con disallow-doctype-decl a true, cualquier documento con <!DOCTYPE ...> provoca una excepción y no llega a procesarse: adiós a las entidades externas y también al DoS por expansión (sección 4). Las otras banderas son defensa en profundidad para casos donde no puedas prohibir la DTD entera.
- SSRF a través de XXE
Una entidad externa no solo apunta a file://; también puede apuntar a http://. Eso convierte al servidor en un cliente que hace peticiones a donde diga el atacante: es un SSRF (Server-Side Request Forgery, categoría A10, lección 03-12) montado sobre XXE.
<!DOCTYPE factura [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/">
]>
<factura>&xxe;</factura>Esa IP es el servicio de metadatos de muchas nubes; alcanzarlo puede filtrar credenciales temporales de la instancia. El atacante también puede escanear la red interna (http://10.0.0.5:8080/admin). La corrección es la misma (prohibir DTD/entidades externas); profundizaremos en las defensas específicas de SSRF (allowlist de destinos, bloqueo de metadatos) en la lección 03-12.
flowchart LR A[Atacante] -->|XML con entidad http| B[Modulo Java BazarNube] B -->|el parser hace la peticion| C[Servicio interno / metadatos cloud] C -->|respuesta| B B -->|reflejada| A
- DoS: el ataque "billion laughs"
No todos los XXE leen ficheros; algunos buscan agotar recursos. El "billion laughs" define entidades anidadas que se expanden exponencialmente:
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!-- ...hasta lol9: mil millones de "lol" -->
]>
<lolz>&lol9;</lolz>Un documento de pocos KB se expande a gigabytes en memoria, agotando RAM y CPU: denegación de servicio. La defensa vuelve a ser prohibir la DTD (disallow-doctype-decl), que impide definir estas entidades. Si por algún motivo necesitas DTD, limita la profundidad de expansión y el tamaño de entrada.
- Endurecer parsers XML en Java
Java tiene varias APIs XML y cada una debe endurecerse; es un error común blindar DocumentBuilderFactory y olvidar SAXParserFactory, XMLInputFactory (StAX) o TransformerFactory.
| API Java | Ajuste clave |
|---|---|
DocumentBuilderFactory (DOM) |
disallow-doctype-decl = true |
SAXParserFactory |
disallow-doctype-decl = true |
XMLInputFactory (StAX) |
SUPPORT_DTD = false, IS_SUPPORTING_EXTERNAL_ENTITIES = false |
TransformerFactory |
ACCESS_EXTERNAL_DTD = "", ACCESS_EXTERNAL_STYLESHEET = "" |
// StAX endurecido
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false);Muchas librerías modernas de Spring ya vienen endurecidas, pero el módulo legacy de BazarNube usa parsers directos: hay que auditarlos uno a uno.
- Endurecer parsers XML en Node
Node no incluye un parser XML nativo; se usan librerías. Recomendaciones:
- Elige librerías que no procesen entidades externas (o que lo tengan desactivado por defecto), como
fast-xml-parser, que no expande entidades externas del sistema. - Evita librerías basadas en
libxmlsin configurar (noent,nonet); si las usas, no actives la expansión de entidades ni el acceso a red.
// Node — parseo XML sin entidades externas
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({ processEntities: false }); // no expande entidades
const data = parser.parse(xmlString);
- Alternativas y defensa en profundidad
- Prefiere JSON cuando puedas elegir el formato: no tiene el concepto de entidades externas y elimina la clase XXE por completo.
- Valida contra un esquema (XSD) el XML entrante para rechazar estructuras inesperadas.
- Menor privilegio y segmentación: el módulo Java no debería poder leer ficheros sensibles ni alcanzar la red interna/metadatos (defensa que se solapa con A10).
- Registra y alerta ante XML con DTD/
<!DOCTYPE>(enlaza con A09, 03-11): en un flujo normal no deberían aparecer.
Errores Comunes y Consejos
- Confiar en la config por defecto del parser. En muchos parsers, las entidades externas están activas por defecto.
- Blindar solo una API XML. Endurece DOM, SAX, StAX y Transformer; cualquiera olvidada reabre el agujero.
- Pensar que solo lee ficheros. XXE también hace SSRF (metadatos cloud) y DoS.
- Asumir "el XML viene de un proveedor de confianza". El canal puede interceptarse o el proveedor comprometerse; valida siempre.
- Filtrar
<!DOCTYPE>con regex. Frágil; usa las banderas del parser (disallow-doctype-decl). - Consejo: si el flujo no necesita DTD (lo normal), prohíbela por completo: es la mitigación más simple y contundente.
Ejercicios
Ejercicio 1. Explica qué hace este documento y qué ajuste del parser lo neutraliza:
Ejercicio 2. El equipo endureció DocumentBuilderFactory pero sigue habiendo XXE en un flujo que usa TransformerFactory para transformar XML. ¿Por qué? ¿Cómo se corrige?
Ejercicio 3. ¿Por qué prohibir la DTD (disallow-doctype-decl) mitiga a la vez la lectura de ficheros, el SSRF vía XXE y el "billion laughs"?
Soluciones
Solución 1. Define una entidad externa e que apunta al fichero secrets.yml y la referencia en el cuerpo: si el parser expande entidades externas, filtra el contenido del fichero de secretos. Se neutraliza con disallow-doctype-decl = true (o, como refuerzo, desactivando las entidades generales externas).
Solución 2. Porque el endurecimiento es por API: blindar el DocumentBuilderFactory no afecta al TransformerFactory, que tiene sus propias propiedades. Se corrige fijando factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "") y ACCESS_EXTERNAL_STYLESHEET, "". Hay que auditar todas las rutas de parseo/transformación XML.
Solución 3. Los tres ataques dependen de la DTD: las entidades externas (lectura de ficheros y SSRF) se declaran en <!DOCTYPE ... [ <!ENTITY ...> ]>, y las entidades anidadas del "billion laughs" también. Si el parser rechaza cualquier documento con DTD, ninguna de esas construcciones llega a procesarse. Por eso es la mitigación más contundente cuando no necesitas DTD.
Conclusión
El XXE es un recordatorio de que las funcionalidades "potentes" de un formato (las entidades externas de XML) se vuelven armas cuando el parser las procesa sobre datos no confiables. La defensa es clara y barata: prohibir la DTD y las entidades externas en todos los parsers XML, preferir JSON cuando sea posible, validar con esquema y aplicar menor privilegio/segmentación de red. En BazarNube, endurecer el InvoiceParser del módulo legacy cerró de golpe lectura de ficheros, SSRF y DoS.
Entrada de backlog — XXE: endurecidos todos los parsers XML del módulo Java de facturación (disallow-doctype-decl, entidades externas off, Transformer sin acceso externo); en Node, processEntities: false; pendiente evaluar migrar el flujo de facturas a JSON.
Hasta ahora los fallos venían de nuestro código y configuración. Pero buena parte de una aplicación moderna es código de terceros: librerías y dependencias. La siguiente lección aborda A06:2021 – Componentes Vulnerables y Desactualizados, revisando el package.json y el pom.xml de BazarNube.
Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web
Módulo 1: Introducción a OWASP
Módulo 2: Principales Proyectos de OWASP
- OWASP Top Ten
- OWASP ASVS (Application Security Verification Standard)
- OWASP SAMM (Software Assurance Maturity Model)
- OWASP ZAP (Zed Attack Proxy)
- Otros Proyectos Clave: WSTG, Cheat Sheets y Dependency-Check
Módulo 3: OWASP Top Ten 2021 en Profundidad
- A01:2021 – Pérdida de Control de Acceso
- A02:2021 – Fallos Criptográficos y Exposición de Datos Sensibles
- A03:2021 – Inyección
- Cross-Site Scripting (XSS) en Profundidad
- A04:2021 – Diseño Inseguro
- A05:2021 – Configuración de Seguridad Incorrecta
- Entidades Externas XML (XXE)
- A06:2021 – Componentes Vulnerables y Desactualizados
- A07:2021 – Fallos de Identificación y Autenticación
- A08:2021 – Fallos de Integridad de Software y Datos (Deserialización Insegura)
- A09:2021 – Fallos de Registro y Monitorización
- A10:2021 – Server-Side Request Forgery (SSRF)
Módulo 4: OWASP ASVS (Application Security Verification Standard)
- Introducción a ASVS
- Niveles de Verificación
- Requisitos de Seguridad
- Implementación de ASVS en Proyectos
Módulo 5: OWASP SAMM (Software Assurance Maturity Model)
Módulo 6: OWASP ZAP (Zed Attack Proxy)
- Introducción a ZAP
- Instalación y Configuración
- Escaneo de Vulnerabilidades
- Automatización de Pruebas de Seguridad
Módulo 7: Buenas Prácticas y Recomendaciones
- Ciclo de Vida de Desarrollo Seguro (SDLC)
- Modelado de Amenazas (Threat Modeling)
- Integración de Seguridad en DevOps (DevSecOps)
- Capacitación y Concienciación en Seguridad
- Herramientas y Recursos Adicionales
Módulo 8: Ejercicios Prácticos y Casos de Estudio
- Ejercicio 1: Identificación de Vulnerabilidades
- Ejercicio 2: Implementación de Controles de Seguridad
- Caso de Estudio 1: Análisis de un Incidente de Seguridad
- Caso de Estudio 2: Mejora de la Seguridad en una Aplicación Web
