Skip to content

XXE (XML External Entity) Injection: How It Works and How to Fix It

XXE (XML external entity) injection explained: where it hides, what it exposes, the parser settings that stop it, and how we test for it from $5,200.

Mark KissBy , Founder & CEOUpdated 12 min read

Web Application Penetration TestingFrom $5,200Fixed scope, free retest, no hourly billing.

Get my fixed price

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:

  1. Disable DTDs on every parser that reads untrusted input, and reject documents containing a DOCTYPE.
  2. Where DTDs are required, disable external general and parameter entities, external DTD loading and XInclude.
  3. Set entity expansion and document size limits, since XXE hardening alone does not stop every XML denial-of-service attack.
  4. Fail closed: if a parser does not support a security setting, stop rather than parse partly hardened.
  5. Treat Office files, SVGs and SAML assertions as XML input.
  6. Do not rely on schema (XSD) validation; entities are resolved while the document is read.
  7. Patch XML libraries, SOAP stacks and SSO components, and add a static analysis rule that flags unhardened parsers.
  8. 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.

Mark Kiss

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 →

All articlesApplication Pentesting

Find out what an attacker sees.

Tell us what to test and see your fixed price.

Prefer the full scoping questionnaire? 
Start the conversation

Get a quick proposal

We are online