Skip to main content

XXE - XML External Entity Processing

An XML parser is left in a configuration that resolves external entities, so a document the attacker controls can name a SYSTEM entity and make the parser read a local file or issue a request from the server. Findings are reported under API8:2023 Security Misconfiguration.

What Vulkro detects

Vulkro runs several parser-specific XXE detectors. All of them are affirmative configuration shapes rather than taint proofs: the detector reads the parser construction, it does not run the parser.

Python: an lxml.etree.XMLParser constructed with any of resolve_entities=True, no_network=False, or load_dtd=True (High severity, High confidence, no taint required). XXE-PY-001 (High) covers an xml.sax parser that affirmatively enables the external general or parameter entity feature, and is suppressed when the file imports defusedxml.

JavaScript: XXE-JS-001 (High) fires on a libxmljs / libxmljs2 parse called with noent: true, which turns on libxml2 entity substitution. XXE-JS-002 (Medium, heuristic) fires on xmldom / @xmldom/xmldom DOMParser().parseFromString(...), which is Medium precisely because the installed runtime version cannot be read from source.

Go: XXE-GO-001 (Medium, heuristic) fires on a parse through a third-party libxml2 binding. The stdlib encoding/xml never resolves external SYSTEM or DTD entities, so a binding is the only Go XXE vector, and reachability with untrusted input is not proven.

Java: JAVA-XXE-001 (High severity, Medium confidence) fires when a JAXP DocumentBuilderFactory / SAXParserFactory / XMLInputFactory / TransformerFactory, a dom4j SAXReader, or a JDOM SAXBuilder parses a request-derived value in a file where no hardening feature is set anywhere. If any hardening call is present in the same file the detector stays silent, because a single textual pass cannot prove which parser instance it guards.

What this does not claim: a finding says the parser is configured to resolve external entities (or, for Java, that no hardening is visible in the file), not that a working exploit exists. The entity-limit / billion-laughs denial-of-service variant is a separate, lower-severity reliability signal.

Remediation. Use the hardened wrapper for your language: defusedxml in Python, a factory with disallow-doctype-decl and FEATURE_SECURE_PROCESSING set in Java, libxml2 parsed without NOENT and with network access off, or the Go stdlib encoding/xml.

Non-compliant code (examples)

Python - lxml parser with every protection flipped off

from lxml import etree
parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=False)
doc = etree.fromstring(request_body, parser) # SYSTEM entities resolve

Compliant code (examples)

Python - defusedxml, or lxml with the knobs set safely

from defusedxml.lxml import fromstring
doc = fromstring(request_body)

# If lxml must stay, be explicit:
# parser = etree.XMLParser(resolve_entities=False, load_dtd=False, no_network=True)

See also

  • Confidence model - what High, Medium, and Low mean for findings in this category.
  • Safety - what Vulkro does and does not access on your machine.

References


This page is generated by vulkro rules export <out-dir> from Vulkro's built-in detector catalogue. Edits made by hand are overwritten on the next regeneration.