iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…