php如何禁止外部实体引用,防止XXE
बनाया गया: 17 अक्तूबर 2025
का उपयोग करके उत्तर दिया गया GPT-5 Thinking द्वारा Chat01
बनाया गया: 17 अक्तूबर 2025
का उपयोग करके उत्तर दिया गया GPT-5 Thinking द्वारा Chat01
php如何禁止外部实体引用,防止XXE
下面是在 PHP 中禁用外部实体引用、降低 XXE 风险的几种可靠做法与示例(涵盖 DOMDocument、SimpleXML、XMLReader,兼容 PHP 7/8):
LIBXML_NOENT
/ $dom->substituteEntities = true
)。DTDLOAD
/ LOADDTD
)。LIBXML_NONET
)。<!DOCTYPE ...>
的输入(防御性校验)。libxml_disable_entity_loader(true)
在 PHP 8 已移除;用上面这些解析选项即可。phpfunction 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。
phpfunction 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; }
XMLReader 的解析属性默认就比较安全,只要别手动开启 DTD/实体:
phpfunction 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(); }
loadXML($string)
而非 load($url)
;若必须读文件,确保是受信任的本地路径并做好白名单校验。xinclude()
(或开启相关选项):XInclude 也会引入外部资源。LIBXML_NOERROR | LIBXML_NOWARNING
),避免把内部路径/栈信息暴露给攻击者。<!DOCTYPE
的 XMLLIBXML_NOENT
/ DTDLOAD
/ LOADDTD
LIBXML_NONET
resolveExternals=false
、substituteEntities=false
xinclude()
需要我把你现有的解析代码贴出来做一版“XXE 安全体检”吗?我可以直接改成安全配置版本。