PHP如何禁用外部实体加载来防御XXE libxml_disable_entity_loader函数

4次阅读

Libxml_disable_entity_loader在php 7.4+已移除且完全失效;正确方式是针对simpleXMLdomDocument分别显式配置禁用外部实体,或PHP 8.0+统一使用libxml_set_external_entity_loader(NULL)。

PHP如何禁用外部实体加载来防御XXE libxml_disable_entity_loader函数

libxml_disable_entity_loader 在 PHP 7.4+ 已被移除

直接调用 libxml_disable_entity_loader(true) 在 PHP 7.4 及更新版本会触发 Deprecated: libxml_disable_entity_loader(): this function is deprecated 警告,且**完全失效**。它只在 PHP 5.6–7.3 有效,但即便在这些版本中,也仅影响部分 libxml API(如 simplexml_load_string),对 DOMDocument::loadXML() 等默认行为无效——因为后者不自动继承该全局开关。

正确禁用外部实体的三种可靠方式

必须针对具体 XML 解析器显式配置,不能依赖全局函数:

  • SimpleXML 场景:使用 LIBXML_NOENT | LIBXML_DTDLOAD 以外的选项,并确保不传入 LIBXML_NOENT(它会加载外部实体);更安全的是显式禁用 DTD:
    $xml = simplexml_load_string($raw, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA);
  • DOMDocument 场景:必须在实例化后、调用 loadXML()load() 前设置属性:
    $dom = new DOMDocument(); $dom->resolveExternals = false; $dom->substituteEntities = false; $dom->setEntityLoader(function () { return false; }); // PHP 8.0+ $dom->loadXML($raw);
  • PHP 8.0+ 统一方案:使用 libxml_set_external_entity_loader(null) 替代已废弃函数,它作用于当前请求上下文,对所有后续 libxml 调用生效(包括 SimpleXML 和 DOM):
    libxml_set_external_entity_loader(null); $xml = simplexml_load_string($raw); // 安全 $dom = new DOMDocument(); $dom->loadXML($raw); // 安全

为什么只设 LIBXML_NONET 不够

LIBXML_NONET 阻止网络请求,但无法阻止本地 DTD 文件或内联实体定义(如 &xxe; 指向 file:///etc/passwd)。真实 XXE 利用常通过如下方式绕过:

 ]> &xxe;]]>

此时若未禁用 DTD 解析或实体替换,仍会泄露文件内容。必须组合使用:resolveExternals = false + substituteEntities = false + libxml_set_external_entity_loader(null)

遗留系统兼容 PHP 7.2–7.3 的折中写法

若无法升级 PHP 版本,需同时处理全局开关和实例配置,避免遗漏:

if (function_exists('libxml_disable_entity_loader')) {     libxml_disable_entity_loader(true); } $dom = new DOMDocument(); $dom->resolveExternals = false; $dom->substituteEntities = false; $dom->loadXML($raw);

注意:此写法在 PHP 8.0+ 会报弃用警告,上线前务必确认 PHP 版本并清理该调用。

实际防御 XXE 的关键不在“关一个开关”,而在于每个 XML 解析入口都显式关闭实体加载能力——尤其是当代码路径涉及多个解析器(如先用 SimpleXML 再转 DOM),容易漏掉某一处。

text=ZqhQzanResources