VNX-PHP-013: PHP XXE via LIBXML_NOENT or LIBXML_DTDLOAD flag
Detects XML parsing configured for entity expansion: simplexml_load_*(), new SimpleXMLElement(), DOMDocument load/loadXML and XMLReader open/xml with LIBXML_NOENT or LIBXML_DTDLOAD (inline, across lines or via a flags variable), substituteEntities/resolveExternals set to true, XMLReader SUBST_ENTITIES/LOADDTD enabled, and libxml_disable_entity_loader(false).
| Field | Value |
|---|---|
| Rule ID | VNX-PHP-013 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | PHP |
| CWE | CWE-611 |
| CAPEC | CAPEC-221 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | xxe xml php |
| Source | internal/sast/rules/vnx-php-013.rego |
Overview
This rule detects calls to simplexml_load_string() or simplexml_load_file() that pass the LIBXML_NOENT or LIBXML_DTDLOAD flags. LIBXML_NOENT instructs the parser to substitute XML entity references, including external entities that reference file paths and URLs, with their declared values. LIBXML_DTDLOAD enables loading of external Document Type Definition files, which in turn can declare external entities.
An XML External Entity (XXE) attack works by embedding a DTD in the XML document that declares an entity pointing to a local file (file:///etc/passwd) or a remote URL. When the parser processes the document with entity expansion enabled, the content of the referenced resource is injected into the XML data and may be returned in the application’s response or used in a way that leaks it to the attacker.
PHP’s libxml-based parsers (simplexml, DOMDocument, XMLReader) all share the same underlying entity loader. The libxml_disable_entity_loader(true) function (available in PHP versions before 8.0, where it became a no-op as loading is disabled by default) globally prevents external entity loading for the current request.
Why This Matters
XXE has appeared on the OWASP Top 10 and has been exploited in high-profile attacks against financial services, healthcare, and government systems. The vulnerability is especially dangerous because it operates at the parser level, before any application logic runs, and can expose files the application has never explicitly tried to read.
Common targets in XXE attacks include /etc/passwd and /etc/shadow for credential enumeration, application configuration files containing database credentials, SSH private keys, cloud metadata endpoints (http://169.254.169.254/latest/meta-data/) for IAM credential theft, and internal network services accessible from the server (SSRF).
PHP applications that process SAML assertions, RSS/Atom feeds, Office Open XML documents, or any XML-based API integration are at risk. SAML-based single sign-on is a particularly high-value target because XXE in the SAML parser can enable authentication bypass.
What Gets Flagged
// FLAGGED: LIBXML_NOENT enables entity substitution, exposes local files
$xml = simplexml_load_string($userInput, 'SimpleXMLElement', LIBXML_NOENT);
// FLAGGED: LIBXML_DTDLOAD enables external DTD, which can declare entities
$doc = simplexml_load_file($uploadedFile, 'SimpleXMLElement', LIBXML_DTDLOAD);
// FLAGGED: DOMDocument with the flags on the following lines
$dom->loadXML(
$body,
LIBXML_NOENT | LIBXML_DTDLOAD
);
// FLAGGED: flags in a variable, and the equivalent DOM properties
$opts = LIBXML_NOENT | LIBXML_NONET;
simplexml_load_string($data, 'SimpleXMLElement', $opts);
$dom->substituteEntities = true;
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, true);
libxml_disable_entity_loader(false);
Not flagged: default parsing, LIBXML_NONET/LIBXML_NOCDATA, a flag cleared with ~LIBXML_NOENT, the properties set to false, and the constant named in a string or comment.
A malicious XML payload:
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<root>&secret;</root>
Remediation
-
Remove
LIBXML_NOENTandLIBXML_DTDLOADflags from allsimplexml_load_*andDOMDocument::loadXML()calls. -
Call
libxml_disable_entity_loader(true)at the start of any request that parses XML (for PHP versions before 8.0). -
In PHP 8.0+, external entity loading is disabled by default, ensure you have not re-enabled it with
libxml_set_external_entity_loader(). -
Validate that XML documents do not contain DOCTYPE declarations when your protocol does not require them, reject any document that includes a
<!DOCTYPE>element.
<?php
// SAFE: entity loader disabled and dangerous flags omitted
if (PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader(true);
}
$xml = simplexml_load_string(
$userInput,
'SimpleXMLElement',
LIBXML_NOERROR | LIBXML_NOWARNING // no LIBXML_NOENT or LIBXML_DTDLOAD
);
if ($xml === false) {
throw new InvalidArgumentException('Invalid XML input');
}
References
- CWE-611: Improper Restriction of XML External Entity Reference
- CAPEC-221: Data Serialization External Entities Blowup
- OWASP XXE Prevention Cheat Sheet
- OWASP PHP Security Cheat Sheet
- PHP Manual – libxml_disable_entity_loader()
- PortSwigger Web Security Academy – XXE Injection
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-php-013
// nosec vnx-php-013
Or record an ignore rule: vulnetix ignore add --rule vnx-php-013 --reason "...". See Suppressing findings.