Skip to content
RESEARCH INDEX BREACHROAD / INTELLIGENCE NOTE

XXE: XML parser security in practice

How external entities turn an XML parser into a file reader and HTTP client. Where XML sneaks in, how to disable DTDs, and how to test and detect XXE.

PUBLIC RESEARCH
AUTHOR
/ CEO of Breachroad · OSCP · PNPT
PUBLISHED
25 July 2026
READING TIME
16 min read
TOPIC
Penetration Testing and AppSec
XXE: XML parser security in practice

XXE is often treated as a historical vulnerability, because “everybody uses JSON now”. The illusion comes from where XML actually lives today: not in visible APIs, but in document formats, electronic invoices, data exchange feeds, identity federation protocols and configuration files. An application without a single endpoint that accepts XML can still parse XML a dozen times a day — while reading a spreadsheet or verifying a login token.

The core problem is always the same. The XML standard includes a document type definition mechanism, and within it external entities — a construct that tells the parser to fetch content from a given address and splice it into the document. Enabled by default in some parsers, it turns a data-reading library into a local file reader and an HTTP client running with server privileges.

What the parser actually does

An XML document may declare a document type definition containing entities — named shortcuts expanded during parsing. An internal entity substitutes text defined in the document itself. An external entity points at a resource: a file on disk or a network address. A parser that supports the mechanism performs the fetch before the application sees any data.

There are also parameter entities, used inside the document type definition itself. They matter because they allow constructions where externally fetched content influences further processing — the basis for variants that work without returning results directly to the attacker.

The key design observation: document type definition processing is unnecessary in 99% of business use cases. Applications exchange data, not documents requiring DTD validation. That is why the best fix is not filtering but switching the whole mechanism off.

Impact, most common first

Reading files from the server. The classic outcome: the contents of a configuration file, a private key or a credentials file end up in the application response or in an error message.

SSRF from inside the network. The parser issues a request to an address given in the document. From the internal network’s perspective this is a trusted client, so it reaches services unavailable from outside. In the cloud, the target is usually the instance metadata service and the prize is temporary role credentials.

Denial of service. Nested internal entities expand exponentially and can exhaust process memory in a fraction of a second. This variant needs no network or file access, so it works even where external entities are disabled but expansion limits were never configured.

Out-of-band channel. When parsing results never return to the sender, locally read content can be exfiltrated inside a network request address, and the mere presence of a DNS lookup confirms the vulnerability.

Code execution. Rare and stack-dependent — possible where the parser supports address schemes capable of launching a process. Treat it as a special case, not the rule.

Where XML sneaks in unnoticed

This is the most important part of the topic, because inventory determines whether the fix succeeds.

Office documents. Office suite formats are ZIP archives containing XML files. Every “upload a spreadsheet” or “import a document” feature is an XML parser in disguise. The same goes for preview generation and metadata extraction.

SVG. Vector graphics are XML documents. An SVG avatar upload, conversion to raster, thumbnail generation — all of it goes through a parser.

Invoices and document exchange. Structured electronic invoices, banking messages and exchange formats in logistics and healthcare are XML-based. These paths tend to be the best protected on paper and the weakest technically, because they were built as integrations “with a trusted partner”.

Identity federation. SAML messages are XML processed before authentication. That makes the parser one of the most exposed components in the entire application. There is also an ordering nuance: if the document is parsed before signature verification — and it must be, in order to find the signature at all — then a parser vulnerability is reachable with no credentials whatsoever.

Feeds and imports. RSS/Atom feed import, sitemap files, configurations exported from other systems.

Content type negotiation. An endpoint designed for JSON often sits on a framework that will politely use an XML parser when the request header asks for XML. This is a frequent and surprising vector: the application “does not accept XML” until somebody asks it to.

The fix: disable the document type definition

The proper fix is configuration and looks similar across stacks: reject documents containing a DTD declaration, and where that is impossible, disable external entity expansion and external definition loading.

In the Java ecosystem this means enabling secure processing on the parser factory and explicitly disallowing DTD declarations and external entities. Repeat the settings for every factory in use: the tree parser, the streaming parser, the transformer and the schema validator, because configuring one does not affect the others.

On .NET, the key step is setting DTD processing to prohibited in reader settings and avoiding legacy classes with default resource resolution.

In Python, the standard modules are not hardened in every mode, so the accepted answer is the defusedxml library, which blocks dangerous constructs outright. With lxml, configure the parser explicitly so it neither loads external definitions nor expands entities.

In PHP, modern versions ship safer defaults than they used to, but it is still worth controlling external resource loading options explicitly rather than relying on the interpreter version.

In the Node.js ecosystem, parser choice settles the matter: libraries that do not implement DTDs at all are the recommended option for external data.

In Go the standard library does not expand external entities, which makes that stack a convenient reference point — size and depth limits still apply.

Regardless of language, one rule holds: parser configuration is code and belongs in review. The most common regression is somebody adding a new processing path with a default-configured parser factory.

Resource limits and isolation

Disabling external entities does not protect against memory exhaustion via internal entities. You need limits: maximum document size, maximum count and depth of expansions, a parsing timeout and a process memory cap.

It also helps to treat parsing user files like running foreign code. A parser handling office documents and graphics should run in a separate process, without credentials, without access to the cloud metadata service and without outbound traffic. Then even a successful external entity call has nowhere to go.

Blocking outbound traffic is exceptionally effective here, because it removes both SSRF and the out-of-band channel — two of the three main impacts.

Testing methodology

Testing starts with inventory: where does XML appear in the system at all? Walk the list from the previous section point by point and check which application features accept files, imports, documents and federation messages.

The second step is checking content type negotiation on endpoints that formally serve JSON. If the framework accepts another format, the attack surface is larger than the documentation suggests.

The third step is verifying parser behaviour in a test environment, with control documents, within an agreed scope. What matters is whether a document type declaration is accepted at all. If a document carrying a DTD is rejected at the door, the topic is closed regardless of library version.

The fourth step covers blind variants. When the application does not return document content, confirmation comes from controlled observation of name resolution or a network request towards test infrastructure. This needs scope agreement, because outbound traffic from production tends to be monitored by the defence team and will raise an incident — which, incidentally, is good news about detection maturity.

The fifth step is checking error messages. A parser can disclose system paths and file fragments inside an exception even when the application itself never returns document content.

Report evidence should be minimal: showing that the parser reached for an external resource is enough to classify the risk. Pulling credential files from production is unnecessary and creates a legal problem for the client.

Detection

The best signal is outbound traffic from the document processing component. A spreadsheet conversion service has no reason to resolve domain names; every such event is a low-noise alert.

The second signal is parser exceptions reporting a disallowed DTD declaration. Their appearance means the protection worked — and that somebody is trying.

The third is parsing time and memory. A sharp increase in consumption on a single request points at entity expansion.

The fourth is anomalies in the size and structure of submitted documents, particularly files far smaller than usual that nevertheless take a long time to process.

Does migrating to JSON solve it

Partly. JSON has no entity mechanism and no document type definition, so the whole XXE family disappears. Other issues remain: deep nesting leading to stack exhaustion, duplicate keys interpreted differently by different libraries, very large numbers, and inconsistent parsing between components. Size and depth limits apply regardless of format.

Where XML stays — and in invoices, office documents and identity federation it will stay for a long time — the only effective route is hard parser configuration.

Schema validation without document type definitions

A common objection to disabling document type definitions is that “we have to validate document structure”. That is a misunderstanding born of conflating two mechanisms.

DTD validation is the older mechanism and it is the one that carries entities. XML Schema validation is a separate mechanism, far richer in data types, and it does not require allowing a DTD declaration in the input document. The correct configuration looks like this: the document may not declare its own type definition, and the schema is loaded by the application from a local, team-controlled location.

This detail matters for the schema itself too. A schema fetched from an address supplied in the document means the attacker influences the validation rules and where the server sends requests. Schemas should be artefacts deployed with the application, resolving references against a local catalogue rather than the network.

Document transformation follows the same logic. Transformation engines have their own mechanisms for fetching external documents and their own function extensions. If your application transforms user-supplied documents, the processor configuration demands the same discipline as the parser: external references forbidden, extensions disabled.

Once a vulnerability is confirmed, the response logic matches other bugs granting file reads and server-side requests: assume configuration files within the process’s reach were read, rotate secrets, and in a cloud environment revoke role credentials.

Checklist

Every XML parser in the project has DTD processing and external entities explicitly disabled. The configuration is repeated for all factories: parser, transformer, schema validator. JSON endpoints do not accept another format through content negotiation. Office files and vector graphics are treated as XML documents. Parsing has size, depth, time and memory limits. The file processing component has no credentials and no outbound traffic. Parser error messages never reach responses. Outbound traffic from the parser is monitored. Parser configuration is covered by code review and a regression test.

Conclusion

XXE did not disappear along with the fashion for XML, because XML did not disappear — it merely became invisible. The biggest risk today is not an endpoint that accepts documents but a feature nobody thinks of as a parser: a spreadsheet import, an image thumbnail, a login token check. The fix is cheap and durable because it is configuration: disable document type definitions, set limits, cut the parser off from the network. The hard part is inventory — finding every place where the application reads XML without anyone calling it that.


Primary sources: OWASP — XML External Entity Prevention Cheat Sheet, CWE-611: Improper Restriction of XML External Entity Reference, CWE-776: XML Entity Expansion.

SHARE / COPY