Back

XXE Prevention

The Defence Strategy in One Line

Turn off the feature you don't need. The overwhelming majority of XXE is eliminated by configuring every XML parser to reject DTDs entirely—or, where a DTD is genuinely required, to refuse external entity and external DTD resolution. Everything else on this page is defence-in-depth around that single, decisive control.

If you can do only one thing: set your parser to completely disallow DOCTYPE declarations. A document with no DOCTYPE cannot declare entities, which closes file disclosure, SSRF, out-of-band exfiltration, and entity-expansion DoS in one move.

The Layered Model

LayerControlWhat it stops
1. DesignUse JSON/safe formats where possibleRemoves the parser from the attack surface entirely
2. Parser (primary)Disable DTDs / external entitiesFile read, SSRF, OOB exfiltration, most DoS
3. Resource limitsEntity-expansion and size capsBillion laughs and quadratic blowup DoS
4. NetworkEgress filtering, IMDSv2, isolationBlunts SSRF and blind exfiltration impact
5. OperationsPatch, test, WAF, monitorCatches regressions and unknown parsers

Layer 2 is the fix. Layers 1, 3, 4, and 5 exist because real systems use multiple parsers, some code paths genuinely need DTDs, and configurations drift—so you want the blast radius contained even if one parser is missed.

Layer 1: Prefer a Safer Format

The cheapest XML vulnerability to fix is the parser you never invoke. Where you control both ends of an interface, prefer JSON, which has no concept of entities or external references. When you must accept XML, prefer data-only formats without a DTD, and reject any document that arrives with a DOCTYPE before it reaches the business logic. This does not replace parser hardening—attacker-controlled XML still reaches a parser—but it shrinks how many parsers touch untrusted input.

Layer 2: Disable DTDs and External Entities (per parser)

This is the core of XXE prevention. Below is the exact, copy-pasteable hardening for the major stacks. The recurring theme: there is usually a single flag that forbids DOCTYPE entirely (best), and a set of finer flags to disable external resolution when a DTD must be allowed.

Java (JAXP)

Java's default factories are historically unsafe—you must harden them explicitly. The single most robust setting is the disallow-doctype-decl feature, which makes any DOCTYPE throw a parse exception.

DocumentBuilderFactory (DOM)

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

// PRIMARY: reject any document with a DOCTYPE. This alone stops XXE.
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

// Defence-in-depth: explicitly refuse external entities and external DTDs.
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);       // disable XInclude
dbf.setExpandEntityReferences(false);
// Belt-and-braces: forbid access to external protocols.
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

SAXParserFactory (SAX)

import javax.xml.parsers.SAXParserFactory;

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setXIncludeAware(false);

XMLInputFactory (StAX)

import javax.xml.stream.XMLInputFactory;

XMLInputFactory xif = XMLInputFactory.newInstance();
// Do not support external entities...
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
// ...and disable DTD support entirely where the implementation honours it.
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);

TransformerFactory / SAXTransformerFactory / Schema / XPath

import javax.xml.XMLConstants;
import javax.xml.transform.TransformerFactory;

TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

Apply the same ACCESS_EXTERNAL_* restrictions to SchemaFactory, Validator, and XPath processors—they are XML parsers too and are easy to forget.

Python

Python's standard-library XML modules were historically vulnerable, and ElementTree alone does not expose clean flags to disable entity resolution across versions. The maintained, recommended answer is the defusedxml package, which wraps the standard parsers and forbids the dangerous features by default.

Preferred: defusedxml (drop-in replacement)

# pip install defusedxml
import defusedxml.ElementTree as ET

# Raises defusedxml.EntitiesForbidden / DTDForbidden on malicious input.
tree = ET.fromstring(untrusted_xml)

# defusedxml also wraps the other parsers:
#   defusedxml.minidom, defusedxml.sax, defusedxml.pulldom
# and, if installed, defusedxml.lxml

lxml directly (when you need it)

from lxml import etree

# Refuse entity resolution, DTD loading, and any network access.
parser = etree.XMLParser(
    resolve_entities=False,   # do not expand entities
    no_network=True,          # block http/ftp fetches
    dtd_validation=False,
    load_dtd=False,
)
root = etree.fromstring(untrusted_xml, parser=parser)

Caveat: resolve_entities=False stops external entity expansion, but for full protection against the billion-laughs class and malicious DTDs, defusedxml (or defusedxml's lxml wrapper) is the safer default because it forbids DTDs outright.

Standard-library SAX (if you cannot add a dependency)

from xml.sax import make_parser
from xml.sax.handler import feature_external_ges, feature_external_pes

parser = make_parser()
parser.setFeature(feature_external_ges, False)   # external general entities
parser.setFeature(feature_external_pes, False)   # external parameter entities

Even so, the official Python documentation itself recommends defusedxml for untrusted input, because the standard modules do not defend against entity-expansion DoS on their own.

PHP (libxml)

PHP's XML functions sit on top of libxml2. Since libxml2 2.9.0 (2012), external entities are not loaded by default, but you should still make the intent explicit and never enable LIBXML_NOENT on untrusted input.

<?php
// Never pass LIBXML_NOENT for untrusted XML: it turns entity substitution ON.
// Do NOT do this: $doc->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);

// Safe load: no network, no external DTD, no entity substitution.
$doc = new DOMDocument();
$ok = $doc->loadXML($xml, LIBXML_NONET);   // LIBXML_NONET blocks network access
if ($ok === false) {
    // reject malformed / disallowed input
}

// SimpleXML equivalent:
$sxe = simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NONET);
?>

On older PHP/libxml combinations you may also see libxml_disable_entity_loader(true) used to block the external entity loader process-wide. That function is deprecated and a no-op on modern PHP precisely because the secure behaviour is now the default—so on current versions, simply avoid LIBXML_NOENT and LIBXML_DTDLOAD and pass LIBXML_NONET.

.NET

Modern .NET (Core / 5+) is safe by default: XmlReader, XmlDocument, and XDocument do not resolve external entities unless you opt in. On the legacy .NET Framework, XmlDocument and XmlTextReader were unsafe by default and must be hardened.

XmlReader (recommended)

using System.Xml;

var settings = new XmlReaderSettings
{
    // Reject DTDs entirely -- the strongest setting.
    DtdProcessing = DtdProcessing.Prohibit,
    // Do not resolve any external resource (defence in depth).
    XmlResolver   = null,
    MaxCharactersFromEntities = 1024   // cap entity expansion as well
};

using var reader = XmlReader.Create(stream, settings);
var doc = new XmlDocument();
doc.Load(reader);

Legacy XmlDocument (harden explicitly)

using System.Xml;

var doc = new XmlDocument
{
    // Null resolver = no external DTDs, entities, or schemas are fetched.
    XmlResolver = null
};
doc.LoadXml(untrustedXml);

Use DtdProcessing.Prohibit when no DTD is expected (it throws on any DOCTYPE); use DtdProcessing.Ignore plus a null XmlResolver if you must tolerate a DOCTYPE but never resolve anything external.

Layer 3: Limit Expansion and Resources

If a DTD must be permitted for legitimate reasons, you still need caps so that internal-entity expansion (billion laughs, quadratic blowup) cannot exhaust memory:

Layer 4: Network and Platform Controls

Layer 5: Patch, Test, and Monitor

Prevention Checklist

Next Steps