En la lliçó anterior vam veure que el Top Ten 2021 va integrar l'antic XXE dins d'A05 (Configuració de Seguretat Incorrecta), perquè en el fons un XXE és un parser XML mal configurat. Ara obrim aquella caixa amb detall, perquè té tècnica, explotació i remediació pròpies. El XXE (XML External Entities) aprofita una característica de l'estàndard XML —les entitats externes, definides a la DTD— que molts parsers processen per defecte, per fer que el servidor llegeixi fitxers locals, faci peticions de xarxa en nom de l'atacant (SSRF, que veurem a A10) o se saturi (DoS).

L'escenari perfecte per a BazarNube és el mòdul legacy Java/Spring de facturació i comandes, que rep documents XML de proveïdors i sistemes externs. Un XML de factura manipulat es pot convertir en una eina per llegir /etc/passwd, arribar a serveis interns o tombar el servei. Aquest és un dels focus clàssics de codi Java antic.

Avís legal i ètic: els payloads XML són il·lustratius i amb dades fictícies. Practica només sobre sistemes propis o amb autorització explícita. Llegir fitxers del servidor o arribar a la seva xarxa interna sense permís és delicte.

Contingut

  1. Què és una entitat XML i per què és perillosa
  2. XXE de lectura de fitxers al mòdul Java de BazarNube
  3. SSRF a través d'XXE
  4. DoS: l'atac "billion laughs"
  5. Endurir parsers XML en Java
  6. Endurir parsers XML en Node
  7. Alternatives i defensa en profunditat
  8. Errors comuns, exercicis i solucions

  1. Què és una entitat XML

XML permet definir entitats, que són com variables. Una entitat interna és text; una entitat externa apunta a un recurs fora del document mitjançant SYSTEM i una URI (file://, http://). Quan el parser expandeix aquesta entitat, va a buscar el recurs. Aquest comportament, unit a dades controlades per l'atacant, és l'essència del XXE.

<!-- Entitat externa: en expandir-se, el parser LLEGEIX el fitxer indicat -->
<!DOCTYPE factura [
  <!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<factura>&secret;</factura>

Si el parser processa la DTD i expandeix entitats externes, el contingut de /etc/passwd acaba dins del document i, sovint, reflectit a la resposta.

  1. XXE de lectura de fitxers a BazarNube

El mòdul de facturació parseja l'XML entrant amb la configuració per defecte de DocumentBuilderFactory:

// InvoiceParser.java (modul legacy Java) — VULNERABLE a XXE
public Document parse(InputStream xml) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    // Per defecte NO deshabilita DTD ni entitats externes
    DocumentBuilder db = dbf.newDocumentBuilder();
    return db.parse(xml);   // processa entitats externes de l'atacant
}

Com s'explota (il·lustratiu)

Un proveïdor maliciós (o algú que intercepta el flux) envia aquesta "factura":

<?xml version="1.0"?>
<!DOCTYPE factura [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<factura>
  <client>&xxe;</client>
  <total>0</total>
</factura>

En parsejar, el motor expandeix &xxe; llegint /etc/passwd. Si BazarNube retorna després el camp client en algun missatge o log accessible, l'atacant obté el fitxer. Amb variants pot llegir codi font, fitxers de configuració amb credencials o claus.

Correcció: deshabilitar DTD i entitats externes

La manera més robusta en Java és prohibir per complet les DTD (disallow-doctype-decl). Si el flux no necessita DTD (gairebé mai la necessita), això tanca XXE d'arrel:

// InvoiceParser.java — SEGUR
public Document parse(InputStream xml) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    // 1) Prohibir DTD per complet: bloqueja XXE i billion laughs de cop
    dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    // 2) Reforc per si algun flux necessites 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);
}

Amb disallow-doctype-decl a true, qualsevol document amb <!DOCTYPE ...> provoca una excepció i no arriba a processar-se: adéu a les entitats externes i també al DoS per expansió (secció 4). Les altres banderes són defensa en profunditat per a casos on no puguis prohibir la DTD sencera.

  1. SSRF a través d'XXE

Una entitat externa no només apunta a file://; també pot apuntar a http://. Això converteix el servidor en un client que fa peticions a on digui l'atacant: és un SSRF (Server-Side Request Forgery, categoria A10, lliçó 03-12) muntat sobre XXE.

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

Aquella IP és el servei de metadades de molts núvols; arribar-hi pot filtrar credencials temporals de la instància. L'atacant també pot escanejar la xarxa interna (http://10.0.0.5:8080/admin). La correcció és la mateixa (prohibir DTD/entitats externes); aprofundirem en les defenses específiques d'SSRF (allowlist de destinacions, bloqueig de metadades) a la lliçó 03-12.

flowchart LR
  A[Atacant] -->|XML amb entitat http| B[Modul Java BazarNube]
  B -->|el parser fa la peticio| C[Servei intern / metadades cloud]
  C -->|resposta| B
  B -->|reflectida| A

  1. DoS: l'atac "billion laughs"

No tots els XXE llegeixen fitxers; alguns busquen esgotar recursos. El "billion laughs" defineix entitats niades que s'expandeixen exponencialment:

<!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;">
  <!-- ...fins a lol9: mil milions de "lol" -->
]>
<lolz>&lol9;</lolz>

Un document de pocs KB s'expandeix a gigabytes en memòria, esgotant RAM i CPU: denegació de servei. La defensa torna a ser prohibir la DTD (disallow-doctype-decl), que impedeix definir aquestes entitats. Si per algun motiu necessites DTD, limita la profunditat d'expansió i la mida d'entrada.

  1. Endurir parsers XML en Java

Java té diverses APIs XML i cadascuna s'ha d'endurir; és un error comú blindar DocumentBuilderFactory i oblidar SAXParserFactory, XMLInputFactory (StAX) o TransformerFactory.

API Java Ajust clau
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 endurit
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false);

Moltes llibreries modernes de Spring ja vénen endurides, però el mòdul legacy de BazarNube fa servir parsers directes: cal auditar-los un a un.

  1. Endurir parsers XML en Node

Node no inclou un parser XML natiu; es fan servir llibreries. Recomanacions:

  • Tria llibreries que no processin entitats externes (o que ho tinguin desactivat per defecte), com fast-xml-parser, que no expandeix entitats externes del sistema.
  • Evita llibreries basades en libxml sense configurar (noent, nonet); si les fas servir, no activis l'expansió d'entitats ni l'accés a xarxa.
// Node — parseig XML sense entitats externes
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({ processEntities: false }); // no expandeix entitats
const data = parser.parse(xmlString);

  1. Alternatives i defensa en profunditat

  • Prefereix JSON quan puguis triar el format: no té el concepte d'entitats externes i elimina la classe XXE per complet.
  • Valida contra un esquema (XSD) l'XML entrant per rebutjar estructures inesperades.
  • Mínim privilegi i segmentació: el mòdul Java no hauria de poder llegir fitxers sensibles ni arribar a la xarxa interna/metadades (defensa que se solapa amb A10).
  • Registra i alerta davant d'XML amb DTD/<!DOCTYPE> (enllaça amb A09, 03-11): en un flux normal no haurien d'aparèixer.

Errors Comuns i Consells

  • Confiar en la config per defecte del parser. En molts parsers, les entitats externes estan actives per defecte.
  • Blindar només una API XML. Endureix DOM, SAX, StAX i Transformer; qualsevol oblidada reobre el forat.
  • Pensar que només llegeix fitxers. XXE també fa SSRF (metadades cloud) i DoS.
  • Assumir "l'XML ve d'un proveïdor de confiança". El canal es pot interceptar o el proveïdor comprometre's; valida sempre.
  • Filtrar <!DOCTYPE> amb regex. Fràgil; fes servir les banderes del parser (disallow-doctype-decl).
  • Consell: si el flux no necessita DTD (l'habitual), prohibeix-la per complet: és la mitigació més simple i contundent.

Exercicis

Exercici 1. Explica què fa aquest document i quin ajust del parser el neutralitza:

<!DOCTYPE d [ <!ENTITY e SYSTEM "file:///app/config/secrets.yml"> ]>
<d>&e;</d>

Exercici 2. L'equip va endurir DocumentBuilderFactory però encara hi ha XXE en un flux que fa servir TransformerFactory per transformar XML. Per què? Com es corregeix?

Exercici 3. Per què prohibir la DTD (disallow-doctype-decl) mitiga alhora la lectura de fitxers, l'SSRF via XXE i el "billion laughs"?

Solucions

Solució 1. Defineix una entitat externa e que apunta al fitxer secrets.yml i la referencia al cos: si el parser expandeix entitats externes, filtra el contingut del fitxer de secrets. Es neutralitza amb disallow-doctype-decl = true (o, com a reforç, desactivant les entitats generals externes).

Solució 2. Perquè l'enduriment és per API: blindar el DocumentBuilderFactory no afecta el TransformerFactory, que té les seves pròpies propietats. Es corregeix fixant factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "") i ACCESS_EXTERNAL_STYLESHEET, "". Cal auditar totes les rutes de parseig/transformació XML.

Solució 3. Els tres atacs depenen de la DTD: les entitats externes (lectura de fitxers i SSRF) es declaren a <!DOCTYPE ... [ <!ENTITY ...> ]>, i les entitats niades del "billion laughs" també. Si el parser rebutja qualsevol document amb DTD, cap d'aquestes construccions arriba a processar-se. Per això és la mitigació més contundent quan no necessites DTD.

Conclusió

El XXE és un recordatori que les funcionalitats "potents" d'un format (les entitats externes d'XML) es tornen armes quan el parser les processa sobre dades no fiables. La defensa és clara i barata: prohibir la DTD i les entitats externes en tots els parsers XML, preferir JSON quan sigui possible, validar amb esquema i aplicar mínim privilegi/segmentació de xarxa. A BazarNube, endurir l'InvoiceParser del mòdul legacy va tancar de cop lectura de fitxers, SSRF i DoS.

Entrada de backlog — XXE: endurits tots els parsers XML del mòdul Java de facturació (disallow-doctype-decl, entitats externes off, Transformer sense accés extern); en Node, processEntities: false; pendent avaluar migrar el flux de factures a JSON.

Fins ara els errors venien del nostre codi i configuració. Però bona part d'una aplicació moderna és codi de tercers: llibreries i dependències. La lliçó següent aborda A06:2021 – Components Vulnerables i Desactualitzats, revisant el package.json i el pom.xml de BazarNube.

Curs d'OWASP: Directrius i Estàndards per a la Seguretat en Aplicacions Web

Mòdul 1: Introducció a OWASP

Mòdul 2: Principals Projectes d'OWASP

Mòdul 3: OWASP Top Ten 2021 en Profunditat

Mòdul 4: OWASP ASVS (Application Security Verification Standard)

Mòdul 5: OWASP SAMM (Software Assurance Maturity Model)

Mòdul 6: OWASP ZAP (Zed Attack Proxy)

Mòdul 7: Bones Pràctiques i Recomanacions

Mòdul 8: Exercicis Pràctics i Casos d'Estudi

Mòdul 9: Avaluació i Certificació

© Copyright 2026. Tots els drets reservats