XXE (XML external entity) injection is a vulnerability in which an application’s XML parser accepts a document type definition (DTD) from untrusted input and fetches whatever file or URL that DTD’s “external entity” points to. Depending on what the parser can reach, the result is local file disclosure, server-side request forgery into internal systems and cloud metadata, denial of service, and occasionally code execution. The fix is a parser setting, not an input filter: disable DTDs, or at minimum external entities, on every parser that touches untrusted XML.
This guide covers where XXE hides, safe parser defaults, detection, and how a web application penetration test finds it, from $5,200 at a fixed price with a free retest (see the web application penetration testing cost guide). SOAP and other XML-heavy services go through API penetration testing from $4,000.
What is XXE?
XML lets a document carry a DTD that declares entities: named placeholders the parser replaces with content. An internal entity is defined inline; an external entity tells the parser to load content from a URI, such as a local file or a web address.
The vulnerability appears when the document is untrusted. If a parser accepts a DTD from a request and resolves external entities, the attacker decides what the server reads or requests, with the application’s own file permissions and network position. In the classic case, a submitted document declares an external entity pointing at a local file and places it in a field the application echoes back, so the response carries the file. MITRE catalogs this as CWE-611, Improper Restriction of XML External Entity Reference and notes that XXE and SSRF are closely related.
XXE had its own slot in the OWASP Top 10 in 2017 (A4). In 2021 it was folded into Security Misconfiguration, and in the OWASP Top 10 2025 it maps to A02 Security Misconfiguration, alongside CWE-776 for entity expansion. The placement reflects the fix: XXE is a parser configuration flaw, not an escaping problem. Our OWASP Top 10 2025 explainer covers the full list.
Types of XXE attack and what each exposes
| Variant | How it behaves | What it exposes |
|---|---|---|
| In-band (classic) | Fetched content is reflected in the response | Files the app account can read: configuration, keys, source |
| Blind (out-of-band) | Nothing reflected; the parser sends data to an outside server over HTTP or DNS | The same files, plus uncontrolled outbound connections |
| Error-based | File content leaks inside a verbose parser error | Files, and an error-handling weakness |
| XXE to SSRF | The parser requests internal URLs for the attacker | Internal admin pages, internal APIs, cloud instance credentials |
| XInclude | Input is embedded in a server-side XML document, so no DTD is needed | Same as in-band, on endpoints that look like plain text |
| Entity expansion (XEE, CWE-776) | Nested entities multiply in memory, the “billion laughs” pattern | Availability; stopped by disabling DTDs or limiting expansion, not by blocking external entities alone |
The SSRF row is where XXE can reach cloud credentials. If an AWS application server still accepts IMDSv1, a parser that fetches any URL can retrieve the instance role’s temporary credentials, a step MITRE ATT&CK tracks as T1552.005, Cloud Instance Metadata API. Our cloud application security guide covers that path, and our AWS penetration testing guide shows how the metadata service is tested. XXE can also chain into remote code execution.
Where XXE hides
Few teams think of their product as an XML application, which is why XXE survives. We check these first:
- SOAP APIs: still common in banking, insurance and healthcare integrations, so they belong in the scope of fintech penetration testing.
- SAML single sign-on: assertions are XML, parsed before anyone has logged in.
- File uploads: DOCX, XLSX and PPTX files are ZIP archives of XML, and SVG images are XML. Resume parsers, spreadsheet imports and image converters all parse them.
- REST endpoints that also accept XML: many frameworks parse an XML body on an endpoint documented as JSON if the client changes the content type.
- Imports, feeds and generators: catalogs, RSS, XML-RPC, XSLT report builders, HL7 CDA in healthcare (see healthcare penetration testing) and ISO 20022 payment messages in finance.
XXE in the wild: recent exploited cases
CISA added these XXE flaws to its Known Exploited Vulnerabilities (KEV) catalog after confirmed exploitation:
| CVE | Product | Added to CISA KEV | Note |
|---|---|---|---|
| CVE-2024-34102 | Adobe Commerce and Magento Open Source | July 17, 2024 | Unauthenticated; the CVE record says it could allow arbitrary code execution |
| CVE-2025-2775 and CVE-2025-2776 | SysAid On-Prem | July 22, 2025 | Two XXE flaws in an IT service desk platform |
| CVE-2025-58360 | OSGeo GeoServer | December 11, 2025 | Unauthenticated, through the WMS GetMap operation |
Much real XXE exposure sits in third-party software you host, so inventory internet-facing XML services and patch them fast.
How to prevent XXE injection
The OWASP XML External Entity Prevention Cheat Sheet is the reference, and its first rule matters most: disable DTDs whenever possible. A parser that never processes a DTD has no external entities to resolve.
Safe parser defaults by language
| Platform | Default | What to set or check |
|---|---|---|
| Java JAXP (DocumentBuilderFactory, SAXParserFactory) | Processes DTDs unless configured | Set disallow-doctype-decl to true and enable secure processing on every factory |
| Java StAX (XMLInputFactory) | DTD support on | Turn off DTD and external entity support |
| Java XSLT (TransformerFactory) | Can load external DTDs and stylesheets | Enable secure processing; set external DTD and stylesheet access to empty |
| .NET Framework 4.5.2+ and modern .NET | XmlReader, XmlDocument and LINQ to XML safe | Look for a non-null XmlResolver, or ASP.NET apps targeting an older framework |
| libxml2 2.9+ (C, C++ and bindings such as Node.js libxmljs and Ruby Nokogiri) | External entities off | Never pass the NOENT (noent in libxmljs) or DTDLOAD options for untrusted input |
| PHP | Safe with libxml 2.9.0+ | Avoid LIBXML_NOENT and LIBXML_DTDLOAD; libxml_disable_entity_loader was deprecated in PHP 8.0 because the default became safe |
| Python standard library (Expat) | No local file or network access | Use defusedxml for untrusted XML; keep Python and Expat current |
| Python lxml | External general entities off by default in XMLParser since lxml 5.0, and in iterparse() and ETCompatXMLParser since 6.1 (April 2026); external parameter entities loaded by default until 6.1.3 (September 2026) | Use lxml 6.1.3 or later, or pass resolve_entities=False; check for code that re-enables entity resolution |
Today, XXE usually traces to unhardened Java factories, entity resolution switched back on, or an old pinned library.
XXE fix code examples by language
These are the minimum settings for a parser that reads untrusted XML. Each follows the OWASP cheat sheet linked above; adapt the variable names to your code.
Java (DocumentBuilderFactory). Reject any document that carries a DOCTYPE:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Java (StAX). XMLInputFactory has no secure-processing feature, so set both properties:
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
.NET. Prohibit DTDs and remove the resolver, explicitly, so the code stays safe whatever framework version it targets:
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null };
using var reader = XmlReader.Create(stream, settings);
Python. Use defusedxml in place of the standard library parser, or harden lxml:
from defusedxml import ElementTree
tree = ElementTree.parse(untrusted_file, forbid_dtd=True)
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
PHP. Leave entity substitution and DTD loading off, and block network fetches:
$doc = new DOMDocument();
$doc->loadXML($xml, LIBXML_NONET); // never add LIBXML_NOENT or LIBXML_DTDLOAD
To keep the fix in place, add one unit test per parser wrapper that feeds it a document containing a DOCTYPE declaration and asserts that parsing throws. It fails the build the day someone switches entity resolution back on. For Java, the CodeQL query java/xxe flags untrusted XML reaching a weakly configured parser.
XXE prevention checklist
Make these part of your secure coding practices:
- Disable DTDs on every parser that reads untrusted input, and reject documents containing a DOCTYPE.
- Where DTDs are required, disable external general and parameter entities, external DTD loading and XInclude.
- Set entity expansion and document size limits, since XXE hardening alone does not stop every XML denial-of-service attack.
- Fail closed: if a parser does not support a security setting, stop rather than parse partly hardened.
- Treat Office files, SVGs and SAML assertions as XML input.
- Do not rely on schema (XSD) validation; entities are resolved while the document is read.
- Patch XML libraries, SOAP stacks and SSO components, and add a static analysis rule that flags unhardened parsers.
- Add defense in depth: deny-by-default egress from application servers, IMDSv2 required on cloud instances, and a service account that cannot read secrets it does not need.
OWASP ASVS 5.0 makes restrictive XML parser configuration, with external entity resolution disabled, a Level 1 requirement (1.5.1); our OWASP ASVS guide explains how the levels work. On item 8, AWS documents that IMDSv2 needs a session token obtained with a PUT request, and that instances accept either version unless set to require v2. A parser sends a plain GET with no token, so a v2-only instance refuses it. For wider API controls, see our API security best practices.
How to detect XXE attempts
| Log source | What to look for | What it means |
|---|---|---|
| WAF, reverse proxy or API gateway logs | DOCTYPE or ENTITY declarations in request bodies; XML sent to JSON endpoints | Probing (most WAF rules will not see a DTD inside a zipped DOCX) |
| Application and parser error logs | Errors about DTDs or external entities, with file paths | Probing or error-based extraction |
| Egress firewall, proxy, VPC Flow Logs, DNS resolver logs | App servers making outbound HTTP, FTP or DNS requests to unfamiliar hosts | Blind XXE, the clearest signal |
| Sysmon Event ID 3 (network connection, off by default) and 22 (DNS query) | w3wp.exe or java.exe connecting to new external hosts or internal admin ports | The parser is fetching for someone else |
| Linux auditd file watches; Windows Security Event ID 4663 with object auditing | The app account reading system files, SSH keys or configuration it never opens | Successful file disclosure |
| AWS CloudTrail and GuardDuty | The GuardDuty finding InstanceCredentialExfiltration (OutsideAWS or InsideAWS) | Instance credentials taken through SSRF are in use elsewhere |
| Host and container metrics | CPU or memory spikes and worker restarts on XML endpoints | Entity expansion (XEE) |
Initial access maps to MITRE ATT&CK T1190, Exploit Public-Facing Application. Egress logging does the most work: a server that only talks to known hosts makes blind XXE harder and obvious. If a test’s probing raised no alert, that silence is a finding too.
How Invadel tests for XXE
XXE is part of every web and API test we run, under OWASP WSTG-INPV-07 (Testing for XML Injection):
- Map every XML input, including undocumented ones such as REST endpoints that accept XML when the content type changes.
- Confirm with harmless markers: callbacks to infrastructure we control, an agreed non-sensitive file instead of secrets, and no entity-expansion tests on production unless the rules of engagement allow it.
- Measure reach: which files the parser reads, which internal hosts and cloud metadata it reaches. Reach sets severity.
- Report the fix at the parser: endpoint, library and configuration change, mapped to CWE-611, OWASP A02:2025 and ASVS 1.5.1 with a CVSS rating.
With source access, a secure code review finds unhardened parsers no test request reaches; our SAST vs DAST comparison explains what automated scanners miss. For the opposite kind of flaw, where every request is well formed, see our IDOR guide; our web application security testing guide shows how a full test runs.
| Where your XML lives | Test | Fixed price from |
|---|---|---|
| Web app with uploads, SAML SSO or XML imports | Web application penetration test | $5,200 (Medium $7,800) |
| SOAP services, or REST APIs that accept XML | API penetration test (API pricing) | $4,000 |
| Parser configuration across the codebase | Secure code review | $4,800 |
| Desktop app that opens XML-based files | Thick client penetration test | $6,000 |
Each price is fixed in writing. The executive report says what each vulnerable parser could read or reach, and the free retest resends the same harmless markers to confirm the parser no longer resolves or fetches them. An attestation letter and the findings platform are included, onboarding starts within 24 hours of a signed proposal, and the sample report shows the format.
Frequently asked questions
What is XEE? XML entity expansion: a denial-of-service attack in which nested entities expand until they exhaust memory and CPU (the “billion laughs” pattern), tracked as CWE-776. XXE reaches outside the document; XEE overwhelms the parser from inside it.
Is XML safe? XML itself is safe. The risk is a parser that processes DTDs and external entities from untrusted documents.
What does “improper restriction of XML external entity reference” mean? It is the official name of CWE-611, the wording in scanner reports and CVE titles: the software lets external entities point to files or URLs outside its intended control, and the parser fetches them.
How do you prevent XXE injection? Disable DTDs on every parser that handles untrusted XML. If you need DTDs, disable external entities and external DTD loading, keep XInclude off, set expansion limits and patch XML libraries. Egress filtering and IMDSv2 limit the damage if a parser is missed.
How do I fix XXE in Java? Set the disallow-doctype-decl feature to true and enable secure processing on every DocumentBuilderFactory and SAXParserFactory, turn off DTD and external entity support on XMLInputFactory, and enable secure processing on TransformerFactory. Java factories are not safe by default, so each one needs the settings.
Does JSON have an equivalent of XXE? No. JSON has no DTDs or entities, so there is nothing for the parser to fetch. The usual catch is an endpoint documented as JSON that still accepts XML when the content type changes.
Can a vulnerability scanner find XXE? Scanners find simple in-band XXE and some blind cases. They routinely miss XXE in uploaded Office files, SAML flows, multi-step imports and endpoints that parse XML only after a content-type change.
The short version
XXE happens when an XML parser trusts a DTD from untrusted input and fetches the files or URLs it names, exposing local files, internal systems and cloud credentials. Disable DTDs, keep external entities and XInclude off, limit expansion, and back it with egress controls and IMDSv2. To find the parsers you missed, a web application penetration test starts at $5,200 (see the pricing) and an API penetration test at $4,000, each with a free retest: scope yours here.
Written by
Mark Kiss, Founder & CEO
15+ years in the field. Focus: Offensive security & strategy. Part of the team that scopes, tests, and reports for our clients. Full profile →