Skip to content
XML External Entity

XML External Entity (XXE): How a Parser Reads /etc/passwd For You

An XML parser that resolves external entities will fetch any file or URL an attacker names. Here is how XXE turns a document upload into local file disclosure and SSRF, and the parser settings that stop it cold.

Part 1 of 4From the XML External Entity series

On this page

XML is more powerful than most developers realize. Beyond elements and attributes, the format lets a document define entities — and external entities can point at files on disk or URLs on the network. XML external entity (XXE) injection abuses a parser that resolves those entities: by declaring one that references a local file or an internal address, an attacker makes the server read sensitive files, reach internal services, and exfiltrate data. The root cause is depressingly simple — many XML libraries enable external-entity and DTD processing by default.

This post shows how a single crafted document turns a routine XML endpoint into a file-disclosure and SSRF primitive, and how to configure the parser so it never happens.

How it happens

Anywhere an application accepts XML — SOAP services, SAML, document uploads, sitemaps, SVG, config import — it must parse it. The vulnerability appears when the parser is left at its default settings, which often resolve DOCTYPE-declared entities. Consider a Java endpoint that reads an uploaded XML order:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());

String item = doc.getElementsByTagName("item").item(0).getTextContent();

This looks ordinary, and for well-formed orders it works fine. But DocumentBuilderFactory does not disable external entities by default. The parser will dereference any entity the document declares — including one that points outside the document entirely.

A concrete attack

Instead of a normal order, the attacker uploads an XML body with a DOCTYPE that defines an external entity pointing at a local file:

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

When the parser hits &xxe;, it resolves the entity by reading file:///etc/passwd and substitutes the file's contents into the element. Whatever the application does next with that element — echo it back, store it, include it in an error — now carries the file. The response body comes back containing:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...

The same technique swaps the target for an internal URL to turn the parser into a server-side request forger:

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

Now the server fetches cloud metadata on the attacker's behalf — credentials and all.

The attacker did not break the parser. They used a standard XML feature exactly as specified — the parser was simply configured to trust the document about which files and URLs to read.

What an attacker gains

XXE is a versatile primitive that opens several doors at once:

  • Local file disclosure — read /etc/passwd, source code, configuration files and application secrets directly off the server's disk.
  • Server-side request forgery — make the server hit internal-only services and cloud metadata endpoints (like 169.254.169.254) that are unreachable from outside.
  • Out-of-band exfiltration — with parameter entities and an attacker-hosted DTD, steal data even when the response shows nothing (blind XXE).
  • Denial of service — entity-expansion bombs can exhaust memory and CPU.

On many stacks, file disclosure alone hands over the keys: a leaked database password or cloud credential is enough to compromise far more than the XML endpoint.

The fix: disable DTDs and external entities in the parser

XXE is one of the rare classes with a clean, definitive fix: turn off the dangerous features at the parser. Since the application almost never needs DOCTYPEs or external entities, disable them outright. In Java:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
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();
Document doc = db.parse(request.getInputStream());

With disallow-doctype-decl set, the earlier payload is rejected before any entity is resolved — there is nothing to dereference. The same principle applies on every platform:

  • Disable DTD processing and external-entity resolution in whichever XML library you use; most expose a single flag or a secure-processing mode.
  • Prefer simpler data formats like JSON where you control the contract — they have no equivalent of external entities.
  • Keep XML libraries patched, since defaults and bypasses change between versions.
Module: XXE. How it probes: Submits external-entity and parameter-entity payloads to every XML-accepting endpoint the crawl finds, including SOAP and WSDL surfaces. How it confirms: File-read evidence in the response, or an out-of-band entity fetch when the parser answers silently. A finding moves from Possible to Confirmed only after this check. Reported as CWE-611 · ATT&CK T1190 · SARIF 2.1.0.
How SelfSec proves this one
Module
XXE
How it probes
Submits external-entity and parameter-entity payloads to every XML-accepting endpoint the crawl finds, including SOAP and WSDL surfaces.
How it confirms
File-read evidence in the response, or an out-of-band entity fetch when the parser answers silently.
Reported as CWE-611 · ATT&CK T1190 · SARIF 2.1.0

A reflected response alone is not enough to promote a XML External Entity finding. The classical engine has to confirm the behavior before it reaches the report. See the detection engine

SelfSec scanner

Find XML External Entity in your own app

Reading about the class is one thing. SelfSec tests for it against an application you are authorized to assess, and only calls it real when independent evidence agrees.

Find it Confirmed, not guessed 21 vulnerability modules, with the classical engine owning the verdict. Prove it Evidence you can re-read Every finding keeps the raw request, the response and the baseline it was measured against.
Join the launch list