Improper Restriction of Recursive Entity References in DTDs (XML Entity Expansion)
Description
Improper Restriction of Recursive Entity References in DTDs occurs when a product uses XML documents and allows their structure to be defined with a Document Type Definition (DTD), but does not properly control the number of recursive definitions of entities. XML allows defining entities that reference other entities, creating the potential for exponential expansion. The classic "Billion Laughs" attack defines nested entities where each level references the previous multiple times, causing a small XML document to expand into gigabytes of data when parsed, consuming all available memory and CPU resources.
Risk
XML Entity Expansion attacks can bring down servers with minimal attack payload. A few hundred bytes of malicious XML can expand to gigabytes, exhausting memory instantly. CVE-2023-52426 in libexpat (affecting versions through 2.5.0) demonstrated that even well-established XML libraries can be vulnerable. Recent vulnerabilities have affected IBM Maximo Application Suite, Meinberg LANTIME firmware, Dell PowerProtect Data Manager, and IBM Rational ClearCase. This attack is particularly dangerous because it requires no authentication, works against any XML-parsing endpoint, and the payload is small enough to bypass most size-based protections.
Solution
Disable DTD processing entirely when external XML is accepted. Set entity expansion limits in XML parsers. In Java, use XMLConstants.FEATURE_SECURE_PROCESSING and setProperty for entity limits. In Python, use defusedxml library. In PHP, use libxml_disable_entity_loader(). Configure parsers to limit total entity expansions and nested entity depth. If DTDs are required, implement strict limits on entity expansion count and total expansion size. Consider using JSON or other formats that don't support entity expansion for external data interchange.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Denial of Service Exponential entity expansion consumes all memory, crashing the application or server. |
| System Resources | Scope: CPU Exhaustion Processing exponentially expanding entities consumes CPU cycles. |
| Financial | Scope: Resource Costs In cloud environments, memory and CPU consumption can trigger significant costs. |
Example Code + Solution Code
Vulnerable Code
<!-- VULNERABLE: Billion Laughs Attack Payload -->
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
<!-- This ~1KB file expands to ~3GB of "lol" strings -->
// VULNERABLE: Default XML parser allows entity expansion
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class XMLProcessor {
public Document parseXML(String xmlInput) throws Exception {
// Default parser is vulnerable to billion laughs!
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new InputSource(new StringReader(xmlInput)));
}
}
# VULNERABLE: Standard XML libraries allow entity expansion
from xml.etree import ElementTree
def parse_xml(xml_string):
# ElementTree doesn't limit entity expansion by default
root = ElementTree.fromstring(xml_string)
return root
# VULNERABLE: lxml without protection
from lxml import etree
def parse_xml_lxml(xml_string):
parser = etree.XMLParser() # Default allows expansion
root = etree.fromstring(xml_string.encode(), parser)
return root
<?php
// VULNERABLE: DOMDocument allows entity expansion
function parseXML($xmlString) {
$dom = new DOMDocument();
$dom->loadXML($xmlString); // Processes entities!
return $dom;
}
?>
Fixed Code
// SAFE: Disable DTDs and limit entity expansion
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.XMLConstants;
public class SecureXMLProcessor {
private static final int MAX_ENTITY_EXPANSIONS = 100;
public Document parseXMLSafe(String xmlInput) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Disable DTDs entirely (best protection)
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// If DTDs must be allowed, set strict limits
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// Set entity expansion limit
factory.setAttribute("http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit",
MAX_ENTITY_EXPANSIONS);
// Disable external entities
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.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new InputSource(new StringReader(xmlInput)));
}
}
// SAFE: SAX parser with limits
import org.xml.sax.XMLReader;
public XMLReader createSecureSAXReader() throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
// Disable DTDs
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
XMLReader reader = factory.newSAXParser().getXMLReader();
// Additional limits
reader.setProperty("http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit", 100);
return reader;
}
# SAFE: Use defusedxml library
import defusedxml.ElementTree as ET
def parse_xml_safe(xml_string):
# defusedxml blocks entity expansion attacks by default
# Raises EntitiesForbidden if entities are found
root = ET.fromstring(xml_string)
return root
# SAFE: Configure lxml to prevent expansion
from lxml import etree
def parse_xml_lxml_safe(xml_string):
parser = etree.XMLParser(
resolve_entities=False, # Don't resolve entities
no_network=True, # No network access
dtd_validation=False, # Don't validate DTD
load_dtd=False, # Don't load external DTD
huge_tree=False # Limit tree size
)
root = etree.fromstring(xml_string.encode(), parser)
return root
# SAFE: Set global limits
import defusedxml
defusedxml.defuse_stdlib() # Patches standard library
# Now standard library is safer
from xml.etree import ElementTree
root = ElementTree.fromstring(xml_string) # Protected
<?php
// SAFE: Disable entity loading
function parseXMLSafe($xmlString) {
// Disable external entity loading
$previousValue = libxml_disable_entity_loader(true);
// Disable entity substitution
$dom = new DOMDocument();
$dom->substituteEntities = false;
// Use options to limit parsing
$options = LIBXML_NOENT | LIBXML_NONET | LIBXML_DTDLOAD;
// Suppress errors to prevent information disclosure
libxml_use_internal_errors(true);
$result = $dom->loadXML($xmlString, $options);
// Restore previous setting
libxml_disable_entity_loader($previousValue);
if (!$result) {
$errors = libxml_get_errors();
libxml_clear_errors();
throw new Exception('Invalid XML');
}
return $dom;
}
// SAFE: Use XMLReader with limits
function parseXMLStream($xmlString) {
$reader = new XMLReader();
// Set options before opening
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, false);
$reader->XML($xmlString, null, LIBXML_NOENT | LIBXML_NONET);
// Process document
while ($reader->read()) {
// Process nodes
}
$reader->close();
}
?>
Exploited in the Wild
libexpat Recursive Expansion (libexpat, 2024)
CVE-2023-52426 in libexpat through 2.5.0 allows recursive XML Entity Expansion when XML_DTD is undefined, affecting numerous applications that use this widely-deployed library for XML parsing.
IBM Maximo Application Suite (IBM, 2025)
XML Entity Expansion vulnerability in IBM Maximo Application Suite Manage Component allows attackers to cause denial of service through crafted XML documents.
Dell PowerProtect Data Manager (Dell, 2025)
CWE-776 vulnerability in Dell PowerProtect Data Manager enables denial of service through recursive entity expansion in XML processing.
Tools to test/exploit
-
XMLBomb — XML entity expansion test payloads.
-
Burp Suite — test XML endpoints with XEE payloads.
-
OWASP ZAP — automated XXE/XEE testing.
CVE Examples
-
CVE-2023-52426 — libexpat recursive entity expansion.
-
CVE-2013-1643 — PHP SOAP extension XML bomb.
-
CVE-2003-1564 — Original billion laughs attack in XML parsers.
References
-
MITRE. "CWE-776: Improper Restriction of Recursive Entity References in DTDs." https://cwe.mitre.org/data/definitions/776.html
-
OWASP. "XML External Entity (XXE) Processing." https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing