XXE Remediation

Risk Severity
🔴 Critical
Fix Effort
⚡ Low (Quick Fix)
Est. Time
⏱️ 30-60 minutes
Reference
A05:2021 CWE-611

XML External Entity (XXE) attacks exploit vulnerable XML parsers to read local files, perform SSRF, or cause denial of service. The fix is straightforward: disable DTD and external entity processing.

XXE Impact

XXE can lead to reading sensitive files like /etc/passwd or web.config, internal network scanning, denial of service via billion laughs attack, and in some cases remote code execution.

Understanding XXE

Vulnerable XML

xml
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>

Entity &xxe; is replaced with file contents

Attack Variants

  • Classic XXE: Read local files
  • Blind XXE: Out-of-band data exfiltration
  • Error-based XXE: Data in error messages
  • SSRF via XXE: Internal network access
  • Billion Laughs: DoS via entity expansion

Disable External Entities

Simple Fix

The fix for XXE is remarkably simple: disable DTD processing entirely or disable external entity resolution. Most applications don't need DTDs, so disabling is safe.

Java

java
// DocumentBuilderFactory
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.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

// 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);

// XMLInputFactory (StAX)
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

Python

python
# Using defusedxml (recommended - drop-in replacement)
import defusedxml.ElementTree as ET
tree = ET.parse('file.xml')

# Standard library with manual fixes
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse('file.xml', parser)

# For xml.etree (limited protection)
import xml.etree.ElementTree as ET
# Note: xml.etree doesn't support external entities by default,
# but defusedxml provides additional protections

PHP

php
// Disable external entity loading globally
libxml_disable_entity_loader(true);

// For specific parsing
$doc = new DOMDocument();
$doc->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);

// Using XMLReader
$reader = new XMLReader();
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, false);

.NET / C#

csharp
// XmlReader (safe by default in .NET 4.5.2+)
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(stream, settings);

// XmlDocument
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.LoadXml(xml);

// XDocument (safe by default)
XDocument xdoc = XDocument.Parse(xml);

Node.js

javascript
// Using libxmljs2 with safe options
const libxmljs = require('libxmljs2');
const doc = libxmljs.parseXml(xml, {
  noent: false,      // Don't expand entities
  nonet: true,       // Don't fetch network resources
  noblanks: true,
  nocdata: true
});

// Or use a safe parser like fast-xml-parser
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser();
const result = parser.parse(xml);

🧪 Testing Verification

XXE Test Payloads

xml
<!-- File read test -->
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>

<!-- SSRF test -->
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://internal-server/admin">
]>

<!-- Billion laughs DoS test (use with caution) -->
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;">
]>

<!-- Expected result: All should fail or return errors -->

Common Mistakes

Only Blocking file://

Attackers can use http://, ftp://, gopher://, or custom protocols

Correct Approach

Disable all external entity processing at the parser level

Trusting Content-Type

XML can be sent with any content type (application/json, image/svg+xml)

Correct Approach

Apply XXE protection to all XML parsing, regardless of content type

XXE Prevention Checklist

  • ☐ DTD processing disabled in all XML parsers
  • ☐ External entity resolution disabled
  • ☐ XInclude disabled
  • ☐ Using safe libraries (defusedxml, etc.)
  • ☐ All XML endpoints protected (SOAP, SVG, DOCX, etc.)
  • ☐ Content type not trusted for determining parser