php如何禁止外部实体引用,防止XXE

Erstellt am: 17. Oktober 2025

Beantwortet mit GPT-5 Thinking von Chat01

Frage

php如何禁止外部实体引用,防止XXE

Gedanke

Fixing XXE in PHP

The user is asking about disabling external entity references in PHP to prevent XXE vulnerabilities. PHP 8.0 removed the libxml_disable_entity_loader() function, as external entity loading is now disabled by default. To be secure, use LIBXML_NONET when loading XML with DOMDocument, ensuring no external entities are processed.

Preventing XXE in PHP

To prevent XXE in PHP, versions <8 relied on libxml_disable_entity_loader(true) to disable external entities, though by libxml version 2.9.0 (2012), external entity loading was disabled by default. Use LIBXML_NONET to block network requests, and avoid LIBXML_NOENT as it could expand internal entities.

Disabling External Entity References in PHP

To prevent XXE, avoid DTDLOAD and LIBXML_NOENT. The safe option is to block external entity resolution and DOCTYPE, using LIBXML_NONET. In PHP <8, use libxml_disable_entity_loader(true), but this was removed in PHP 8. Instead, set resolveExternals and substituteEntities to false in DOMDocument.

XXE Prevention in PHP 8

In PHP 8, libxml_disable_entity_loader() was removed due to thread safety concerns related to libxml 2.9.0, which disabled external entities by default. For XXE prevention, use LIBXML_NONET to block external network access, avoid using LIBXML_NOENT, and disallow DOCTYPEs. Ensure proper flagging, but avoid libxml_set_external_entity_loader in PHP.

Safely Parsing XML in PHP

To prevent XXE attacks, ensure external DTD loading is disabled using options like LIBXML_NONET, and avoid LIBXML_NOENT. Use XMLReader and DOMDocument with flags like LOADDTD and SUBST_ENTITIES set to false. For PHP 8+, use updated options as libxml_disable_entity_loader is deprecated. Avoid parsing with remote URLs and apply the necessary flags to prevent local file exfiltration. Treat untrusted XML as data, not code.

Thought for 59s

Antwort

下面是在 PHP 中禁用外部实体引用、降低 XXE 风险的几种可靠做法与示例(涵盖 DOMDocument、SimpleXML、XMLReader,兼容 PHP 7/8):


0) 总结要点(先看这个)

  • 绝不要启用实体替换LIBXML_NOENT / $dom->substituteEntities = true)。
  • 不要加载 DTD(不要开启 DTDLOAD / LOADDTD)。
  • 禁用网络访问LIBXML_NONET)。
  • 尽量拒绝含 <!DOCTYPE ...> 的输入(防御性校验)。
  • (针对老版本)libxml_disable_entity_loader(true)PHP 8 已移除;用上面这些解析选项即可。

1) DOMDocument 安全解析示例

php
function parseXmlSafelyWithDOM(string $xml): DOMDocument { // 可选:拒绝含 DOCTYPE 的输入(最直接的防线) if (preg_match('/<!DOCTYPE/i', $xml)) { throw new RuntimeException('DOCTYPE is not allowed.'); } $prevUseInternalErrors = libxml_use_internal_errors(true); // PHP 8+ 已没有 libxml_disable_entity_loader;PHP 7 可做兼容: if (PHP_VERSION_ID < 80000) { $prev = libxml_disable_entity_loader(true); } $dom = new DOMDocument(); // 关键:不要使用 LIBXML_NOENT;加上 NONET 禁网;抑制报错信息泄露 $dom->resolveExternals = false; // 不解析外部实体 $dom->substituteEntities = false; // 不做实体替换 $dom->validateOnParse = false; // 不校验 DTD $ok = $dom->loadXML($xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); if (PHP_VERSION_ID < 80000) { libxml_disable_entity_loader($prev); } libxml_use_internal_errors($prevUseInternalErrors); if (!$ok) { throw new RuntimeException('Invalid XML.'); } return $dom; }

要点解释

  • LIBXML_NONET:禁止通过网络抓取外部实体/DTD(避免 http(s):// 等)。
  • 不设置 LIBXML_NOENT:避免把实体展开成文本(这一步正是 XXE 的切入点)。
  • resolveExternals = false / substituteEntities = false:双保险。
  • 拒绝 <!DOCTYPE>:杜绝外部实体定义(包括 SYSTEM/PUBLIC)与参数实体技巧。

注意:LIBXML_NONET 禁的是网络访问;如果你允许 DTD/实体解析,本地协议如 file:// 仍可能被利用。所以最稳妥的策略是既不加载 DTD,也不替换实体,或直接拒绝一切带 DOCTYPE 的 XML。


2) SimpleXML 安全解析示例

php
function parseXmlSafelyWithSimpleXML(string $xml): SimpleXMLElement { if (preg_match('/<!DOCTYPE/i', $xml)) { throw new RuntimeException('DOCTYPE is not allowed.'); } $prevUseInternalErrors = libxml_use_internal_errors(true); if (PHP_VERSION_ID < 80000) { $prev = libxml_disable_entity_loader(true); } // 不要传 LIBXML_NOENT / DTDLOAD;加 NONET $sx = simplexml_load_string( $xml, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING ); if (PHP_VERSION_ID < 80000) { libxml_disable_entity_loader($prev); } libxml_use_internal_errors($prevUseInternalErrors); if ($sx === false) { throw new RuntimeException('Invalid XML.'); } return $sx; }

3) XMLReader(流式解析,更稳)

XMLReader 的解析属性默认就比较安全,只要别手动开启 DTD/实体:

php
function parseXmlSafelyWithXMLReader(string $xml): void { if (preg_match('/<!DOCTYPE/i', $xml)) { throw new RuntimeException('DOCTYPE is not allowed.'); } $reader = new XMLReader(); // NONET 禁网;不要启用 LOADDTD / SUBST_ENTITIES / VALIDATE $reader->XML($xml, null, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING); $reader->setParserProperty(XMLReader::LOADDTD, false); $reader->setParserProperty(XMLReader::SUBST_ENTITIES, false); $reader->setParserProperty(XMLReader::VALIDATE, false); while ($reader->read()) { // 按需读取节点... } $reader->close(); }

4) 额外建议 & 常见坑

  • 不要从 URL 直接解析:优先用 loadXML($string) 而非 load($url);若必须读文件,确保是受信任的本地路径并做好白名单校验。
  • 不要调用 xinclude()(或开启相关选项):XInclude 也会引入外部资源。
  • 最小化错误回显:生产环境抑制 XML 解析详细错误(LIBXML_NOERROR | LIBXML_NOWARNING),避免把内部路径/栈信息暴露给攻击者。
  • 输入大小限制:限制 XML 长度与实体深度,防止“实体爆炸”(Billion Laughs)等 DoS。
  • 尽量使用白名单格式:若只是想传结构化数据,优先选 JSON 或者定义明确的 XML schema 并在受控环境下校验。

5) 快速核对清单

  • 拒绝含 <!DOCTYPE 的 XML
  • 使用 LIBXML_NOENT / DTDLOAD / LOADDTD
  • 使用 LIBXML_NONET
  • resolveExternals=falsesubstituteEntities=false
  • 不从远程 URL 直接解析;不调用 xinclude()

需要我把你现有的解析代码贴出来做一版“XXE 安全体检”吗?我可以直接改成安全配置版本。

Diesen Q&A teilen