XXE Testing
XXE (XML External Entity) tested defensively: how the vulnerability works, how to find it in code review, and how to prevent it.
Ethical hacking — XXE (defensively)
EXAMPLE
# RoE: defensive learning only. Authorised testing on systems you own or have written permission to test.
# ===== What XXE is =====
# An XML parser configured to resolve EXTERNAL ENTITIES will fetch resources defined inline:
# <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# <foo>&xxe;</foo>
# A vulnerable parser substitutes the file contents into the response.
# Impact:
# - File disclosure (passwd, configs)
# - SSRF (entities can reference http:// URLs -> attacker scans internal network)
# - DoS (billion-laughs attack: nested entities expand exponentially)
# ===== Defense by language / parser =====
# Python (defusedxml — the standard fix):
import defusedxml.ElementTree as ET
tree = ET.parse('input.xml') # disables DTDs + external entities by default
# Python stdlib (manual hardening):
import xml.etree.ElementTree as ET
import xml.sax
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_external_ges, False)
parser.setFeature(xml.sax.handler.feature_external_pes, False)
# Java (JAXP):
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
# .NET:
var settings = new XmlReaderSettings {
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
using var reader = XmlReader.Create(input, settings);
# Node (libxmljs, fast-xml-parser):
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({ processEntities: false });
// libxmljs: parseXml(s, { noent: false, dtdload: false, dtdvalid: false, noblanks: true })
# PHP (libxml):
libxml_disable_entity_loader(true); // PHP < 8.0
$dom = new DOMDocument();
$dom->loadXML($input, LIBXML_NONET | LIBXML_NOENT); // careful with LIBXML_NOENT
# ===== Code review checklist =====
# Search for:
# - XmlReader / XmlDocument / DocumentBuilder without DTD restriction
# - SAX parsers without feature toggles
# - 'XML' in legacy SOAP / RSS / OPML / SVG / DOCX / ODT processors
# - File upload paths that parse XML on upload
# - Image processing that reads SVG (XML)
# Lots of XXE arrives via formats that AREN'T 'just XML' (.docx, .svg, .xlsx are zipped XML).
# ===== Detection rules =====
# WAF / SIEM:
# - Inbound XML containing <!DOCTYPE
# - Inbound XML containing 'SYSTEM' or 'PUBLIC' entity declarations
# - Outbound requests from app servers to internal IPs (SSRF via XXE)
# ===== Mitigations beyond parser hardening =====
# - Network egress allowlist; block outbound to internal ranges
# - Cap XML size + entity expansion limits
# - Validate XML against a strict schema (XSD); reject everything else
# - Use JSON where possible (no entity expansion concept)
# ===== Real-world examples (anonymised) =====
# - SVG uploaded to profile avatars -> XXE -> read AWS metadata service -> credential leak
# - SOAP endpoint with billion-laughs payload -> server CPU pinned
# - DOCX template upload (zipped XML) -> XXE on extraction
# ===== Patterns to internalise =====
# - Disable DTDs and external entities at parser construction
# - Use defusedxml / hardened parsers; never rely on the parser default
# - Egress controls block the worst impact (SSRF + data exfil)
# - Code review for XML libraries periodically; new ones drift
# ===== Pitfalls =====
# - Trusting the parser default — most are unsafe historically
# - Disabling DTD on the SAX parser but not the DocumentBuilder elsewhere
# - LIBXML_NOENT in PHP actually ENABLES entity substitution (counter-intuitive)
# - Forgetting that SVG / DOCX / SOAP / RSS are all XML
Why it matters
XXE is a parser configuration bug. defusedxml in Python, hardened DocumentBuilderFactory in Java, XmlResolver = null in .NET, processEntities: false in JS. Disable DTDs, disable external entities, validate against schemas. The blast radius (SSRF, file read, DoS) is large; the fix is one line per parser.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# XML External Entity — almost gone from modern stacks but still found. # Submit XML payloads referencing external entities; if echoed back, you've found XXE. # Defence: disable external entity resolution in the XML parser (libxml2 / Java JAXB / .NET XmlReader).Try it Yourself »
Discussion
Loading…