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

simplexml_load_string($xml)

no

simplexml_load_string($xml, .., LIBXML_NOENT)

yes

(new DOMDocument)→loadXML($xml)

no

(new DOMDocument)→loadXML($xml, LIBXML_NOENT)

yes

(new DOMDocument)→loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD)

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.

© 2026 Xygeni. All rights reserved