PHP 7.4 必须每次解析前显式调用 libxml_disable_entity_loader(TRUE),因其不持久、不跨请求、不继承,仅此操作可直接阻断 XXE;DOMDocument 和 SimpleXML 均需单独覆盖,漏一则风险依旧。

PHP 7.4 默认仍启用外部实体解析,必须手动禁用,否则任何使用 simplexml_load_string、DOMDocument::loadXML 或 xml_parse 解析用户输入 XML 的代码都存在 XXE 风险。
必须在解析前调用 libxml_disable_entity_loader(TRUE)
这是 PHP 7.4 下最直接有效的防御动作,且需每次解析前显式设置——它不持久,不跨请求,不继承。很多项目只在初始化时调一次,或误以为“设过就一直生效”,结果白防。
-
libxml_disable_entity_loader(TRUE)必须在 任何 XML 加载函数调用之前 执行,例如在simplexml_load_string($input)前 - 若同时用
DOMDocument和SimpleXML,两者都要覆盖:先关 loader,再创建对象、再 load - 注意:该函数返回上一状态(bool),可用于临时切换,但生产环境应始终设为
TRUE,避免误恢复 - PHP 8.0+ 已默认禁用,但 7.4 不会自动降级行为,别指望版本升级能“悄悄修复”
DOMDocument 和 SimpleXML 的实际差异与风险点
两者底层共用 libxml,但默认行为和常见误用模式不同:
-
DOMDocument::loadXML():若未提前调libxml_disable_entity_loader(TRUE),遇到含<!DOCTYPE的输入即触发 XXE -
simplexml_load_string():同样危险,且因语法简洁更易被开发者忽略防护;错误示例:$xml = simplexml_load_string($_POST['data'])—— 没关 loader,没校验内容,直接喂恶意 XML -
XMLReader流式解析相对安全,但它默认仍允许 DTD 解析;需额外设置:$reader->setParserProperty(XMLReader::SUBST_ENTITIES, FALSE) - 不要依赖
libxml_use_internal_errors(TRUE)来“屏蔽报错”——它不阻止实体加载,只隐藏警告
仅靠过滤关键词(如 SYSTEM、ENTITY)不可靠
正则或字符串匹配过滤是典型“补丁式防御”,极易被绕过,且干扰正常业务:
立即学习“PHP免费学习笔记(深入)”;
- 攻击者可用大小写混写(
sYsTeM)、空格插入(S Y S T E M)、制表符、注释包裹(<!-- -->SYSTEM)等方式逃逸 - 过滤可能误杀合法 XML(比如文档中真实出现
SYSTEM字样,或某些工业协议 XML 含固定 ENTITY 声明) - 真正该做的,是在解析层彻底切断实体解析能力,而不是在文本层做“猫鼠游戏”
- 如果业务真需要有限度支持外部实体(极少见),应改用
libxml_set_external_entity_loader()自定义白名单逻辑,而非放行后过滤
验证是否生效的快速检查方式
别只信配置,要实测。在开发或测试环境构造一个最小 payload 并观察响应:
<?xml version="1.0"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <data>&xxe;</data>
若返回了系统文件内容或解析报错中出现 failed to load external entity,说明防护已起效;若直接返回 /etc/passwd 内容,说明 libxml_disable_entity_loader(TRUE) 未执行或执行太晚。
最容易被忽略的是:某些框架(如 Laravel 的早期 XML 中间件)或 Composer 包(如某些 SOAP 客户端)会在内部重新启用 loader,必须审计其源码或打日志确认最终状态。



















