第十章-XXE漏洞
飞书链接:https://icnewi51k2yp.feishu.cn/wiki/N0VgwhvoSiOj2OkWQqVcMirvnmb
[10.1]XXE 漏洞绪论
XXE 漏洞简介
XXE 漏洞全称 XML External Entity Injection 即 xml 外部实体注入漏洞,XXE 漏洞发生在应用程序解析 XML 输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起 dos 攻击等危害。xxe 漏洞触发的点往往是可以上传 xml 文件的位置,没有对上传的 xml 文件进行过滤,导致可上传恶意 xml 文件。
XXE 的核心逻辑是用 XML 的 “外部引用” 功能,骗网站的解析器帮你读取文件、探测内网 —— 本质是解析器 “不设防”,被你利用当工具人。
学会 XXE,我们得先学会 xml 的基本语法
xml 语言
xml 介绍
XML 指可扩展标记语言(EXtensible Markup Language)
把数据以特定的方法结构化后传输给对方,让对方在结构化的数据中进行读取。
与 html 不同的是,xml 标签没有被预定义,需要自行定义标签。

XML 语言形如这样

下面我们稍微介绍一下 xml 的语法
xml 语法
首先要说明的是
1.xml 标签一定是需要闭合的双标签,且 xml 必须进行正确嵌套.
2.XML 标签对大小写敏感

3.XML 文档必须有根元素,根元素的名字通常使用 root,但也不是绝对的

4.XML 无缩进要求 但建议进行首行缩进便于阅读
5.头声明问题

6.实体引用
在 XML 中,一些字符拥有特殊的意义。
如果你把字符’<’放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。

为了避免这个错误,我们必须用实体引用来代替

实体引用的语法

使用 php 解析 xml
simplexml_load_file()函数
simplexml_load_file()函数把 XML 文档载入对象中;print_r 打印显示变量 $xml。
1 |
|
直接打印 $xml,会以类似反序列化的方式打印对象

而 $xml 也可以当做一个对象,引用其中的成员

使用 DOMDocument 读取 XML 文档
数据通常以 POST 方式把字符串提交进去,以 xml 结构提交给目标网站,再通过 php 伪协议读取内容,进行 xml 解析
1 |
|
注意:DOMDocument 是个类,需要先实例化为对象,以调用其成员方法
读取的方法有两种

或者

读取特定节点值的语法结构

混合使用读取 xml 文档

注意
第 3,4 行可以用 load 合并 $doc->load(“1.xml”)
第 5 行又把这个文件转成一个具有成员的对象
DTD 声明
**文档类型定义(DTD)**可定义合法的 XML 文档构建模块。
它使用一系列合法的元素来定义文档的结构。
在 XML 中可以自定义标记,DTD 用来定义我们自己定义的
标记的含义,我们自己定义元素的相关属性的文档。它规定和约束 XML 规则的定义和陈述。
DTD 可被成行地声明于 XML 文档中,也可作为一个**外部引用。**

一些元素要特别记住
!ELEMENT 定义元素
!ATTLIST 定义元素的属性
!NOTATION 定义不被解释为
元素或属性的符号
!ENTITY 定义实体,这些实体
可以再 XML 文档中被引用
外部引用 DTD 文件
可以先把 DTD 的代码写到一个 dtd 文件中,再使用本行进行外部引用

注意框选行的 root 名字可以随便起,不影响程序执行
而 1.DTD 的读取等,可能会引起文件包含漏洞,我们下一节介绍

XML 实体
实体的含义
实体是对数据的引用;根据实体种类的不同,XML 解析器将使用实体的替代文本或者外部文档的内容来替代实体引用。
前面我们也提到过实体引用代替符号
所有实体(除参数实体外)都以一个与字符(&)开始,以一个分号()结束。
字符实体
字符实体是约定俗成的,不需要预定义

命名实体
命名实体,类似于变量,需要在 DTD 里面用 <!ENTITY> 语句定义,格式如下

最后一行会返回:

当然,命名实体的定义是可以嵌套的

返回

外部实体
外部调用文件,是 XXE 漏洞产生的主要原因

命名实体的双引号中用 file 伪协议读取文件,前面还要加个 SYSTEM 再引用这个变量 便可以得到这个变量背后的文件内容,从而做到任意文件读取,后续漏洞介绍也会提到
参数实体
参数实体以百分号 % 开头,目的是能够创建替换文本的可重用部分。

等价于

注意分号不能丢了
[10.2]XXE 漏洞利用
注:XXE 靶场在本节作为举例出现的题目,本章实验就不会再出现了
判断 XXE 漏洞的方法
一般的 XXE 漏洞,是基于伪造请求头的
1.读源码,观察是否有读取 xml 相关的 php 代码
实验中 GHCTF 那题是一个很好的读源码例子

2.用 Burp 抓包,观察 get/post 请求中有没有 xml 格式的代码
假如题目是一个登录界面,要输入用户名和密码

随便输入用户名和密码,点击登录并使用 Burp 抓包,观察到 xml 格式

3.判断回显位
直接从前端判断,回显了’admin’,则’admin’为回显位

使用 Repeater 看响应头,同样找到回显位’admin’

4.伪造请求,让 admin 的位置为一个外部实体,采用特殊 xml 代码,让这个实体代表 file 伪协议所读的文件

5.提交请求,则会回显文件内容
下面我们再用一个示例来梳理这些步骤
使用外部实体读取文件(file 协议)
使用外部实体读取文件的步骤,在上面已经说的很清楚,但有时候回显位需要用多样的方式判断
我们一起来看看这道例题
[例题]第十章 天机符阵
题目有个提示:
flag 在 flag.txt 里
我们需要使用 XXE 任意文件包含的方式来读取该文件
拿到题目,我们先输一个 1,什么也没回显

注意到下面有可能定义好的格式
1 | <阵枢>引魂玉</阵枢> |
我们再将格式复制进去看看回显,注意添加根元素
1 | <root> |
在前端发现两个回显位 2,3

我们再抓包至 repeater 确认一下

的确回显了 2,3,因为是中文,所以显示的全是方框
下一步我们需要伪造请求
创建变量名 value 读取 flag.txt 文件,随后引用变量
注意这个 data 可以随意修改,但这个 value 下面要照抄
1 |
|
即得 flag

至于这个文件路径如何找到的,因为我尝试读取过相对路径下 flag(file://flag.txt),失败了,发现了这么一个路径

利用参数实体和 HTTP 协议绕过 file 过滤
外部实体跟在 SYSTEM 后的文件可以使用 http 协议,因此可以自搭一服务器放上一条 DTD 规则
并使用 http 协议去读取它
这样就用了一个变量代表你需要注入的 DTD 格式,并通过引用这个变量来执行它
因此这需要用不同的服务器事先放好需要的代码,比较难操作,放张图在这理解吧

利用 XXE 进行 SSRF 利用
核心还是用到 http 协议,且这里的地址可以填内网的另一台主机甚至发 get 请求
内网另一台主机返回的回显会回显至具有 XXE 漏洞的网站

利用 PHP 伪协议读取源代码等文件
用 file 伪协议读取正确的 php 文件,它会直接被执行,不能查阅内容
而相比较 file 伪协议,PHP 伪协议可以对读取出的文件进行数据编码。
1 |
|
其中 cat+index.php 用于查看 index 的源代码
第三章的时候学过,这里要用 base64 的形式读取文件,否则 php 文件会直接被执行了
PHP 伪协议举例,可以见 2026 春秋杯冬季赛第七题
[10.3]实验:XXE 漏洞实践
我们继续把实验做完
moectf 10 第十章 天机符阵
10.2 节漏洞举例
moectf 10 第十章 天机符阵_revenge
flag 换了个位置,这次在根目录了
1 |
|

做题启示
flag 的位置因题而异,可以按之前那道题的思路找 flag,也要找根目录下是否有 flag
GHCTF 2025
这题比较综合,要自己阅读源代码,如果你用 Burp 抓包的方法,还要自己构造 post 请求,我自己也开了很多遍靶机才做出来
题目没换行,我们先给它换个行
1 | from flask import Flask,request |
读题,这代码蛮难看懂的,需要一定审计能力,借助了一下 AI,大概理解了:
我们要用 POST 方式去访问/ghctf(用 get 它就给你报错) 第 14 行 xml 应该可以通过递表单修改,但是不知道是 get 还是 post,还是都行
因为我们访问这个路由都得用 post,所以下面的 xml 必须要用 post 提交
这是一点,第二点是下面的 name,你得通过代码审计判断出这是要求你的 xml 数据一定要有 name 这个标签,而且通过后续的提交发现回显位也是 name
法一 Hackbar 方法
一开始用 Hackbar 投 xml,发现回显位就是 name,确实回显了 1
1 | <root><name>&value;</name></root> |

但添加了木马以后问题就来了
1 | <root><name>&value;</name></root> |
它不认了

我特意没有换行,怕像课上说的一样换行会出问题,结果最后还是错
于是我又把目光投向到空格,这没准要 URL 编码
我又把他 URL 编码了一番
1 | %3C!DOCTYPE%20data%20%5B%3C!ENTITY%20value%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%5D%3E%3Croot%3E%3Cname%3E%26value%3B%3C%2Fname%3E%3C%2Froot%3E |
运行通过了

法二 Burp 方法
有时候代码太长确实不太想换行,我又考虑了一下 Burp 方法伪造 POST 请求,但这题有点麻烦
我们用内嵌浏览器访问题目,发现默认是 get

要伪造 POST 请求首先这里的 get 要改成 post
其次还需在最后一行添加
1 | Content-Type: application/x-www-form-urlencoded |
最后另起一行,写好 xml 值
像这样

下面的把他 URL 编码了

点提交,就过了

这样就不用换行
不过也麻烦,因为没有现成的 POST 模板给你改,你得自己做一个
做题启示
1.post 提交 xml 值的时候,不能换行,且需要 URL 编码
2.Burp 构造 POST 时要注意这三点,第一是 GET 改 POST,第二是增加必要的那行代码,第三就是 URL 编码
3.从始至终我们一直没离开 URL 编码,因为我们这次是用 get 和 post 改表单数据,而前面的题是在输入框提交
,有的没有动到表单数据
?ctf [Week3] 查查忆
这题平台有问题,做不了
– 周六刚完事 XXE 第二节,今天做 UniCTF,发现 web 解最多的那题是 XXE 漏洞,借助 AI 做对了,也是今天唯一做对的题,很巧,题也很巧妙,结合了一下文件上传,下周会补一下做题感受和 WP
![CVE-2022-47615[任意文件读取]](/img/BqvBbcdufoB3S8xJ1FQcnMsenkh.png)

