XML Entity Expansion (Billion Laughs / XXE)
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). Detection hints list semgrep and psalm as tools that can detect XML parsing without entity expansion limits (e.g., LIBXML_NOENT not disabled, simplexml_load_string without restrictions). These are SAST/specialist tools rather than default linters or compiler errors, placing this squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix describes calling libxml_disable_entity_loader(true) and setting LIBXML_NONET flags — a small, localized change at each XML parsing call site. This is slightly more than a single-line patch because it may need to be applied at multiple parsing locations across a component, but does not require cross-cutting refactors.
Closest to 'localised tax' (b3). The fix applies only to XML parsing call sites. While it applies across web, api, and cli contexts, the actual code change is confined to wherever XML is parsed. It doesn't reshape architecture or slow down unrelated work streams — future maintainers only need to remember this when adding new XML parsing code.
Closest to 'serious trap' (t7). The misconception field states developers believe entity expansion is 'only a theoretical DoS risk,' when in fact a crafted document can expand from kilobytes to gigabytes crashing the server in milliseconds. The common_mistakes also note that developers assume expansion attacks only apply to XXE (external entity loading), when internal entity expansion is equally dangerous — this contradicts how developers reason about the XXE/entity distinction, making it a serious cognitive trap.
Also Known As
TL;DR
Explanation
Two related XML attacks: the Billion Laughs DoS defines nested entities that expand exponentially (lol1 = 10×lol0, lol2 = 10×lol1 ... lol9 = billions of characters), exhausting server memory parsing a tiny document. XXE (XML External Entity) uses <!ENTITY ext SYSTEM 'file:///etc/passwd'> to read local files or <!ENTITY ext SYSTEM 'http://internal/'> to trigger SSRF. PHP's libxml is vulnerable by default. Mitigations: call libxml_disable_entity_loader(true) (PHP < 8.0) or use LIBXML_NONET | LIBXML_NOENT flags with simplexml_load_string() / DOMDocument::loadXML(). In PHP 8.0+ external entity loading is disabled by default. Never parse untrusted XML with a SAX/DOM parser that hasn't had entity expansion disabled.
Common Misconception
Why It Matters
Common Mistakes
- Parsing user-supplied XML without setting LIBXML_NOENT to disable entity expansion.
- Using SimpleXML or DOMDocument on untrusted input without calling libxml_disable_entity_loader(true) first.
- Not setting resource limits (memory, CPU time) for XML parsing operations.
- Assuming entity attacks only apply to XXE — expansion attacks do not require external entity loading.
Avoid When
- Never parse user-supplied XML with unlimited entity expansion enabled — a single crafted document can exhaust server memory.
- Do not use DOMDocument or SimpleXML on untrusted input without configuring safe parse options first.
When To Use
- Disable entity expansion when parsing untrusted XML using LIBXML_NOENT awareness and libxml_disable_entity_loader().
- Set a maximum entity expansion depth/count limit when processing XML from external sources.
Code Examples
$xml = simplexml_load_string($untrusted); // XXE possible pre-PHP 8
libxml_disable_entity_loader(true); // PHP < 8.0
$xml = simplexml_load_string($untrusted, 'SimpleXMLElement', LIBXML_NOENT | LIBXML_NONET);