XML parsed with entity substitution turned on
ID |
php.insecure_xml_parser_configuration |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Misconfiguration |
Language |
Php |
Tags |
ASVS50:v1.5.1, CWE:611, CWE:776, OWASP:2025:A02 |
Description
An XML document is parsed with LIBXML_NOENT, so the parser substitutes the external entities the
document declares and retrieves the files or URLs they name. A document from an untrusted source can then
make the process read a local file or issue a request of the attacker’s choosing.
Rationale
php.xml_entity_injection reports XXE when a data path can be followed from an external input to the
parse. That needs the input to be visible: a parse in a codec or a helper, where the caller lives in
another project, has no source to follow and is reported by nothing. This detector reports the option,
which is unsafe on its own terms.
Its severity is low because the finding says nothing about whether attacker-controlled XML actually
reaches the parse — when that can be shown, php.xml_entity_injection reports it as critical, and this
finding is dropped as a duplicate.
Only the explicit option is reported
For PHP this is the whole design, not a detail. Entity substitution is off unless it is asked for, so the absence of a hardening flag is safe and reporting it would fire on every correct PHP codebase.
Measured on PHP 8.3 with libxml 2.9.14, using an entity that points at a file of known content so that expansion is observed rather than inferred from the absence of an error:
| Call | Entity expanded |
|---|---|
|
no |
|
yes |
|
no |
|
yes |
|
yes |
LIBXML_NOENT is looked for across the arguments rather than at a fixed index, because the options
parameter sits at a different position per function and is usually an | of constants.
Examples
Reported:
$doc = new DOMDocument();
$doc->loadXML($xml, LIBXML_NOENT); // reported
Not reported — the default does not substitute entities:
$doc = new DOMDocument();
$doc->loadXML($xml);
Not reported — these options tune whitespace and error reporting:
$doc->loadXML($xml, LIBXML_NOBLANKS | LIBXML_NOERROR);
Remediation
Drop LIBXML_NOENT. Nothing else is needed on a current PHP: the parser does not resolve external
entities without it.
$doc = new DOMDocument();
$doc->loadXML($xml);
Where a document genuinely uses internal entities, substitute them without allowing external ones by
keeping LIBXML_NOENT off and expanding in application code instead.
Limitations
libxml_disable_entity_loader(false) is not reported. It is deprecated since PHP 8.0, and on the libxml
version current PHP ships the behaviour it toggled is governed by LIBXML_NOENT — but that
interaction was not measured, so the call is left alone rather than reported on an unverified premise.
An options mask built at run time, or passed in as a variable, is not resolved: only a mask naming the constant at the call site is seen.