XML External Entities (XXE)
Also known as: XXE, XML external entity injection, XML entity expansion abuse
A flaw in which an XML parser is configured to resolve external entities while reading untrusted XML, letting the document make the server read local resources or reach other systems; it is prevented by disabling DTDs and external entities in the parser.
How it works
XML external entity injection is a flaw in how an application configures its XML parser. XML allows a document to declare entities, named substitutions that the parser expands while reading. Some of those entities can point to an external resource, such as a local file or a URL. If the parser is allowed to resolve them while processing a document from an untrusted source, the document's author decides what the server reads or contacts.
The root cause is a permissive parser configuration, not a bug in the application's business logic. Many older parsers shipped with entity resolution switched on by default, so the exposure appears wherever XML arrives: file uploads, SOAP and other web service requests, SVG and office documents, single sign-on messages and configuration imports. The impact depends on what the parsing process can reach, from disclosure of local files and internal service data to denial of service through runaway expansion and requests to internal hosts.
The fix is to turn the dangerous capability off rather than to filter input. Disable document type definitions entirely where they are not needed, or at minimum disable external general and parameter entities and external schema loading. Current libraries often do this by default, but older versions and some options re-enable it, so the setting must be explicit and checked. Where possible, accept JSON instead of XML.
Defence is layered. A hardened parser configuration is the primary control, input size and time limits reduce abuse of expansion, a service account with minimal file and network access caps the damage, outbound egress filtering stops the server from reaching internal or arbitrary hosts, and logging plus WAF rules catch attempts that slip through.
Walk through it
- 1Read the parser setup
- 2Name the root cause
- 3Read the signals, pick the fix
- Find every XML entry point
- Harden, restrict and verify
A pre-release review flags the claims intake service. It accepts XML documents from partners and uploads. Read the parsing code as a reviewer and note how the parser is configured before it touches the document.
1public Document parse(InputStream upload) throws Exception {2 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();3 // no feature flags set: library defaults apply4 dbf.setExpandEntityReferences(true);5 dbf.setNamespaceAware(true);6 DocumentBuilder builder = dbf.newDocumentBuilder();7 return builder.parse(upload); // untrusted partner XML8}Spot it
- XML parse errors about unresolved or disallowed entity references, clustered from one source against an upload or web service endpoint.
- WAF matches for XML entity or document type declarations in request bodies that normally carry plain data.
- The application process opening local files outside its usual directories, or reading sensitive system files, while handling a request.
- Outbound connections from the parsing service to internal hosts, metadata endpoints or unfamiliar external addresses during XML processing.
- Unusually slow responses, memory spikes or parser timeouts on small uploads, which can indicate runaway entity expansion.
WAF
ts=2026-10-11T10:02:11Z rule=xml-entity-declaration action=log src=203.0.113.77 host=intake.fabrikam-claims.example path=/claims/upload
ts=2026-10-11T10:02:19Z rule=xml-entity-declaration action=log src=203.0.113.77 host=intake.fabrikam-claims.example path=/claims/uploadApplication and host telemetry
ts=2026-10-11T10:02:12Z svc=intake level=error msg="XML parse failure: external entity reference" client=203.0.113.77
ts=2026-10-11T10:02:13Z proc=intake-svc event=file_open path_class=outside_upload_dir note=unexpected_for_this_service
ts=2026-10-11T10:02:14Z proc=intake-svc event=net_connect dest_class=internal_unlisted note=first_seensplSplunk: sources with repeated XML entity parse errors
index=app sourcetype=intake level=error "external entity"
| stats count by client
| where count > 10Tune the threshold to the endpoint's baseline and correlate with WAF matches and host file or network events before escalating.
kqlKQL: WAF hits for XML entity rules
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog" and ruleGroup_s has "XML"
| summarize hits=count() by clientIp_s, requestUri_s
| where hits > 10Stop it
Disable DTDs and external entities in every XML parser
Turn off document type definitions entirely where possible, or at minimum disable external general entities, external parameter entities and external schema or stylesheet loading. Set this explicitly per parser instead of trusting library defaults, and enforce it in review and static analysis.
Use hardened parser configuration, current libraries and simpler formats
Enable the parser's secure-processing mode, keep XML libraries up to date, and apply the same settings to every component that parses XML, including SVG, office-document and SAML handling. Where the data does not need XML, accept JSON instead.
Validate input and run the parser with least privilege
Limit document size, nesting and parse time, validate against a fixed schema you control, run the service with minimal file and network access, and filter outbound traffic so a missed flaw cannot reach internal systems.
Disable DTDs and external entities on the factory
Vulnerable
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
return builder.parse(upload);Hardened
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
return builder.parse(upload);With document type declarations disallowed, there is nothing for the parser to resolve. Apply the equivalent settings to every parser type in use.
Use a parser that refuses entity processing
Vulnerable
from lxml import etree
tree = etree.parse(upload)Hardened
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False, huge_tree=False)
tree = etree.parse(upload, parser)Or use the defusedxml package, which wraps the standard parsers with safe defaults.
- Disable DTD processing and external entity resolution explicitly on every XML parser, factory and reader in the codebase.
- Turn on the parser's secure-processing mode and keep XML libraries patched; re-check after upgrades.
- Prefer JSON or another simpler format for new interfaces that do not need XML features.
- Cap request size, element nesting and parse time to blunt entity-expansion denial of service.
- Run parsing services as a low-privilege account with read access only to what they need and no access to secrets.
- Filter outbound traffic from application servers so they cannot reach internal management, metadata or arbitrary hosts.
- Add a static-analysis rule that flags parsers created without the hardening flags.
If it already happened
Block the offending sources, tighten WAF rules on the XML endpoint, restrict outbound traffic from the service, and disable the upload feature if exposure is likely while the fix ships.
Disable DTDs and external entities on every affected parser, rotate any credentials or keys the service account could read, and review host and network logs to scope what was read or contacted.
Redeploy the hardened service, run regression tests that confirm entity references are rejected, and notify affected parties if regulated data was exposed.
Add a static-analysis rule and a review checklist item for parser configuration, inventory every place XML is parsed, and keep the log detections above permanently.
Check yourself
1. What is the root cause of an XXE vulnerability?
2. Which control is the primary fix for XXE?
3. Why should the service that parses XML run with least privilege and restricted outbound access?