In the previous lesson we saw that the 2021 Top Ten folded the former XXE into A05 (Security Misconfiguration), because at its core an XXE is a misconfigured XML parser. Now we open that box in detail, because it has its own technique, exploitation and remediation. XXE (XML External Entities) abuses a feature of the XML standard —external entities, defined in the DTD— that many parsers process by default, to make the server read local files, make network requests on the attacker's behalf (SSRF, which we'll see in A10) or exhaust itself (DoS).
The perfect scenario for BazarNube is the legacy Java/Spring billing and orders module, which receives XML documents from suppliers and external systems. A tampered invoice XML can become a tool to read /etc/passwd, reach internal services or take the service down. This is one of the classic hotspots of old Java code.
Legal and ethical notice: the XML
payloadsare illustrative and use fake data. Practice only on systems you own or have explicit authorization to test. Reading server files or reaching its internal network without permission is a crime.
Contents
- What an XML entity is and why it's dangerous
- File-reading XXE in BazarNube's Java module
- SSRF through XXE
- DoS: the "billion laughs" attack
- Hardening XML parsers in Java
- Hardening XML parsers in Node
- Alternatives and defense in depth
- Common mistakes, exercises and solutions
- What an XML entity is
XML lets you define entities, which are like variables. An internal entity is text; an external entity points to a resource outside the document via SYSTEM and a URI (file://, http://). When the parser expands that entity, it goes and fetches the resource. That behavior, combined with attacker-controlled data, is the essence of XXE.
<!-- External entity: when expanded, the parser READS the indicated file -->
<!DOCTYPE invoice [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<invoice>&secret;</invoice>If the parser processes the DTD and expands external entities, the contents of /etc/passwd end up inside the document and, often, reflected in the response.
- File-reading XXE in BazarNube
The billing module parses the incoming XML with the default configuration of DocumentBuilderFactory:
// InvoiceParser.java (legacy Java module) — VULNERABLE to XXE
public Document parse(InputStream xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// By default it does NOT disable DTD or external entities
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(xml); // processes the attacker's external entities
}How it's exploited (illustrative)
A malicious supplier (or someone intercepting the flow) sends this "invoice":
<?xml version="1.0"?>
<!DOCTYPE invoice [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<invoice>
<customer>&xxe;</customer>
<total>0</total>
</invoice>When parsing, the engine expands &xxe; by reading /etc/passwd. If BazarNube later returns the customer field in some accessible message or log, the attacker gets the file. With variants they can read source code, configuration files with credentials, or keys.
Fix: disable DTD and external entities
The most robust approach in Java is to forbid DTDs entirely (disallow-doctype-decl). If the flow doesn't need a DTD (it almost never does), this closes XXE at the root:
// InvoiceParser.java — SECURE
public Document parse(InputStream xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 1) Forbid DTDs entirely: blocks XXE and billion laughs at once
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 2) Extra hardening in case some flow needs a partial DTD:
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);
}With disallow-doctype-decl set to true, any document containing <!DOCTYPE ...> throws an exception and is never processed: goodbye to external entities and also to DoS by expansion (section 4). The other flags are defense in depth for cases where you can't forbid the entire DTD.
- SSRF through XXE
An external entity doesn't only point to file://; it can also point to http://. That turns the server into a client that makes requests wherever the attacker says: an SSRF (Server-Side Request Forgery, category A10, lesson 03-12) built on top of XXE.
<!DOCTYPE invoice [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/">
]>
<invoice>&xxe;</invoice>That IP is the metadata service of many clouds; reaching it can leak the instance's temporary credentials. The attacker can also scan the internal network (http://10.0.0.5:8080/admin). The fix is the same (forbid DTD/external entities); we'll go deeper into SSRF-specific defenses (destination allowlist, metadata blocking) in lesson 03-12.
flowchart LR A[Attacker] -->|XML with http entity| B[BazarNube Java module] B -->|the parser makes the request| C[Internal service / cloud metadata] C -->|response| B B -->|reflected| A
- DoS: the "billion laughs" attack
Not all XXE reads files; some aim to exhaust resources. The "billion laughs" defines nested entities that expand exponentially:
<!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;">
<!-- ...up to lol9: a billion "lol" -->
]>
<lolz>&lol9;</lolz>A document of a few KB expands to gigabytes in memory, exhausting RAM and CPU: denial of service. The defense is again to forbid the DTD (disallow-doctype-decl), which prevents defining these entities. If for some reason you need a DTD, limit the expansion depth and the input size.
- Hardening XML parsers in Java
Java has several XML APIs and each one must be hardened; a common mistake is to lock down DocumentBuilderFactory and forget SAXParserFactory, XMLInputFactory (StAX) or TransformerFactory.
| Java API | Key setting |
|---|---|
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 = "" |
// Hardened StAX
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false);Many modern Spring libraries already ship hardened, but BazarNube's legacy module uses parsers directly: they have to be audited one by one.
- Hardening XML parsers in Node
Node doesn't include a native XML parser; libraries are used. Recommendations:
- Choose libraries that don't process external entities (or have it disabled by default), such as
fast-xml-parser, which doesn't expand system external entities. - Avoid libraries based on
libxmlwithout configuration (noent,nonet); if you use them, don't enable entity expansion or network access.
// Node — XML parsing without external entities
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({ processEntities: false }); // does not expand entities
const data = parser.parse(xmlString);
- Alternatives and defense in depth
- Prefer JSON when you can choose the format: it has no concept of external entities and eliminates the XXE class entirely.
- Validate against a schema (XSD) the incoming XML to reject unexpected structures.
- Least privilege and segmentation: the Java module shouldn't be able to read sensitive files or reach the internal network/metadata (a defense that overlaps with A10).
- Log and alert on XML with a DTD/
<!DOCTYPE>(links to A09, 03-11): in a normal flow they shouldn't appear.
Common Mistakes and Tips
- Trusting the parser's default config. In many parsers, external entities are enabled by default.
- Hardening only one XML API. Harden DOM, SAX, StAX and Transformer; any one forgotten reopens the hole.
- Thinking it only reads files. XXE also does SSRF (cloud metadata) and DoS.
- Assuming "the XML comes from a trusted supplier". The channel can be intercepted or the supplier compromised; always validate.
- Filtering
<!DOCTYPE>with regex. Fragile; use the parser flags (disallow-doctype-decl). - Tip: if the flow doesn't need a DTD (the norm), forbid it entirely: it's the simplest and most decisive mitigation.
Exercises
Exercise 1. Explain what this document does and which parser setting neutralizes it:
Exercise 2. The team hardened DocumentBuilderFactory but there's still XXE in a flow that uses TransformerFactory to transform XML. Why? How is it fixed?
Exercise 3. Why does forbidding the DTD (disallow-doctype-decl) simultaneously mitigate file reading, SSRF via XXE and "billion laughs"?
Solutions
Solution 1. It defines an external entity e pointing to the file secrets.yml and references it in the body: if the parser expands external entities, it leaks the contents of the secrets file. It's neutralized with disallow-doctype-decl = true (or, as reinforcement, by disabling external general entities).
Solution 2. Because hardening is per API: locking down DocumentBuilderFactory doesn't affect TransformerFactory, which has its own properties. It's fixed by setting factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "") and ACCESS_EXTERNAL_STYLESHEET, "". You have to audit all XML parsing/transformation paths.
Solution 3. All three attacks rely on the DTD: external entities (file reading and SSRF) are declared in <!DOCTYPE ... [ <!ENTITY ...> ]>, and so are the nested entities of "billion laughs". If the parser rejects any document with a DTD, none of those constructs get processed. That's why it's the most decisive mitigation when you don't need a DTD.
Conclusion
XXE is a reminder that a format's "powerful" features (XML's external entities) become weapons when the parser processes them over untrusted data. The defense is clear and cheap: forbid the DTD and external entities in all XML parsers, prefer JSON when possible, validate against a schema and apply least privilege/network segmentation. In BazarNube, hardening the legacy module's InvoiceParser closed file reading, SSRF and DoS in one stroke.
Backlog entry — XXE: all XML parsers of the Java billing module hardened (disallow-doctype-decl, external entities off, Transformer with no external access); in Node, processEntities: false; evaluate migrating the invoice flow to JSON still pending.
So far the flaws came from our code and configuration. But a large part of a modern application is third-party code: libraries and dependencies. The next lesson tackles A06:2021 – Vulnerable and Outdated Components, reviewing BazarNube's package.json and pom.xml.
OWASP Course: Guidelines and Standards for Web Application Security
Module 1: Introduction to OWASP
Module 2: Main OWASP Projects
- OWASP Top Ten
- OWASP ASVS (Application Security Verification Standard)
- OWASP SAMM (Software Assurance Maturity Model)
- OWASP ZAP (Zed Attack Proxy)
- Other Key Projects: WSTG, Cheat Sheets and Dependency-Check
Module 3: OWASP Top Ten 2021 in Depth
- A01:2021 – Broken Access Control
- A02:2021 – Cryptographic Failures and Sensitive Data Exposure
- A03:2021 – Injection
- Cross-Site Scripting (XSS) in Depth
- A04:2021 – Insecure Design
- A05:2021 – Security Misconfiguration
- XML External Entities (XXE)
- A06:2021 – Vulnerable and Outdated Components
- A07:2021 – Identification and Authentication Failures
- A08:2021 – Software and Data Integrity Failures (Insecure Deserialization)
- A09:2021 – Security Logging and Monitoring Failures
- A10:2021 – Server-Side Request Forgery (SSRF)
Module 4: OWASP ASVS (Application Security Verification Standard)
Module 5: OWASP SAMM (Software Assurance Maturity Model)
Module 6: OWASP ZAP (Zed Attack Proxy)
- Introduction to ZAP
- Installation and Configuration
- Vulnerability Scanning
- Automating Security Testing
Module 7: Best Practices and Recommendations
- Secure Software Development Life Cycle (SDLC)
- Threat Modeling
- Integrating Security into DevOps (DevSecOps)
- Security Training and Awareness
- Additional Tools and Resources
Module 8: Practical Exercises and Case Studies
- Exercise 1: Identifying Vulnerabilities
- Exercise 2: Implementing Security Controls
- Case Study 1: Analyzing a Security Incident
- Case Study 2: Improving the Security of a Web Application
