XML External Entity (XXE) Processing¶
What does this mean ?¶
XXE occurs when untrusted XML causes a parser to resolve an external entity or another external resource. The application may unintentionally read a file or initiate a request from the server. XML parser behavior depends on the library, runtime version and configuration; enabling a general “secure processing” feature alone is not a portable guarantee.
What can happen ?¶
External resolution can expose local data or reach internal services. Entity expansion and deeply nested documents may exhaust resources even without a successful external request. Validators, XSLT processors and XInclude handling can introduce separate external-resource paths after the initial parse.
Recommendation¶
Reject DTDs when the format does not require them. Explicitly disable external entity, DTD and schema access where supported. Treat failure to apply a required parser setting as a configuration failure rather than continuing with defaults. Do not enable entity substitution for untrusted documents.
Bound request size before parsing and apply resource limits appropriate to the parser. Restrict network egress. If the application needs schema validation, use reviewed local schemas and a resolver policy; do not trust schema locations supplied in the document. JSON is often simpler when XML-specific features are unnecessary.
Sample Code¶
These examples parse bounded, untrusted XML without requiring DTDs. They do not cover every XML processor. Upgrade the parser/runtime and verify its exact behavior in your deployment.
JAXP DocumentBuilderFactory; let unsupported-setting exceptions stop initialization:
static org.w3c.dom.Document parseXml(String raw) throws Exception {
if (raw.length() > 65536) throw new IllegalArgumentException("XML too large");
var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
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);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
var input = new org.xml.sax.InputSource(new java.io.StringReader(raw));
return factory.newDocumentBuilder().parse(input);
}
The previous approach of setting only FEATURE_SECURE_PROCESSING is insufficient across parser implementations.
Modern .NET XmlReader; do not use obsolete ProhibitDtd examples:
static System.Xml.Linq.XDocument ParseXml(string raw)
{
if (raw.Length > 65536) throw new ArgumentException("XML too large");
var settings = new System.Xml.XmlReaderSettings
{
DtdProcessing = System.Xml.DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersInDocument = 65536,
MaxCharactersFromEntities = 1024
};
using var input = new StringReader(raw);
using var reader = System.Xml.XmlReader.Create(input, settings);
return System.Xml.Linq.XDocument.Load(reader);
}
Explicitly prohibiting DTD processing is clearer than silently ignoring a DTD. Do not replace the configured reader with an unreviewed XML load overload later.
With the maintained defusedxml dependency installed:
from defusedxml import ElementTree
def parse_xml(raw: bytes):
if len(raw) > 65536:
raise ValueError("XML too large")
return ElementTree.fromstring(
raw, forbid_dtd=True, forbid_entities=True, forbid_external=True
)
Do not enable external resolution in a different parser downstream. Python's XML security documentation describes which threats apply to each standard-library parser; do not assume all Python XML APIs share the same defaults.
For supported PHP 8 with a maintained libxml, do not request LIBXML_NOENT, LIBXML_DTDLOAD or LIBXML_DTDVALID for untrusted XML:
function parseXml(string $raw): DOMDocument {
if (strlen($raw) > 65536) throw new InvalidArgumentException('XML too large');
$document = new DOMDocument();
$document->resolveExternals = false;
$document->substituteEntities = false;
$previous = libxml_use_internal_errors(true);
try {
if (!$document->loadXML($raw, LIBXML_NONET) || $document->doctype !== null) {
throw new InvalidArgumentException('Invalid or unsupported XML');
}
return $document;
} finally {
libxml_clear_errors();
libxml_use_internal_errors($previous);
}
}
LIBXML_NONET alone does not stop local-file entities. This example keeps external loading and substitution disabled, then rejects documents containing a DTD. The rejection happens after parsing, so body/process resource limits and an updated parser remain necessary. Do not append permissive flags to this call.
Nokogiri uses conservative defaults; keep them and reject DTD-bearing documents:
require 'nokogiri'
def parse_xml(raw)
raise ArgumentError, 'XML too large' if raw.bytesize > 65_536
document = Nokogiri::XML::Document.parse(raw) { |config| config.strict.nonet }
raise ArgumentError, 'DTD not permitted' if document.internal_subset
document
end
Do not enable NOENT, DTDLOAD or HUGE. As with the PHP example, a post-parse DTD check is not a substitute for parser resource controls.
Regression checks¶
Use a harmless local XML fixture and a test resolver that records access without reading files or making network requests. Confirm ordinary XML parses; a DTD declaration or entity reference is rejected according to policy; and no resolver call occurs. Reject oversize input before parsing. Test validators and transforms independently if the application uses them.