外部实体引用
DTD文件用来定义XML文件遵循的规则(格式),而!ENTITY就可实现如同占位符的作用,例如:
]>
&xxe;
此处&xee所要显示的内容即由!ENTITY xee标签决定,在有回显的情况下,该服务器的/etc/passwd文件就会被泄露。
分类
1. 简单情况,有回显
]>
&xxe;
实例:BeeBox中的XXE LOW
抓包可见修改secret的方式是POST一个xml
gambro Any bugs?
而回显的内容中包括用户名,由
可以利用它泄露出/etc/passwd
修改为
]>
&xxe; Any bugs?
白盒:xxe-2.php
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2")
{
ini_set("display_errors",1);
$xml = simplexml_load_string($body);
// Debugging
// print_r($xml);
$login = $xml->login;
$secret = $xml->secret;
...
$message = $login . "'s secret has been reset!";
simplexml_load_string()函数直接将xml解析,login值不经过滤直接接在响应消息前。
其他利用:
-
端口扫描
使用Intruder可以扫描端口
2. 无回显
]
>
&callhome;
www.malicious.com为攻击者搭建的恶意服务器
注:
- 参数实体定义以%作为开头,引用也以%开头,以;结尾,定义和引用都在DTD内部。
- 一般实体的定义无%开头,引用以&开头,以;结尾,定义在DTD,引用在XML。
%xxe读取了/etc/passwd,并将内容附在url: www.malicious.com/后作为一个参数
然后向服务端发送GET请求,其中包含敏感文件内容
实例:FACEBOOK OOB XXE
Facebook招聘页一处.docx文件上传被发现可能存在xxe
注:docx是基于XML的一种压缩文件格式,类似的还有xlsx, pptx
攻击者创建了一个docx文件,并用7zip打开,在其中一个XML中插入payload:
%dtd;
%send;
]]>
在解析的时候,如果受害者开启了外部实体,XML 解析器会调用远程主机。在收到请求调用之后,远程服务器会发送回提前部署好的DTD 文件,像这样:
引入远程dtd文件后,根据上面的payload,/etc/passwd文件内容被附于url
后通过GET请求发送至攻击者服务器。
漏洞的发现者Mohamed 使用 Python 和SimpleHTTPServer开启了一台本地服务器,等待一会后,接收到了以下信息:
SimpleHTTPServer收到了请求/ext.dtd的request,随之受害服务器解析了dtd文件并发送了/FACEBOOK-HACKED? GET请求,遗憾的是并没有和之前预想的附上/etc/passwd的文件内容。原因可能是没有权限
而按照本节最开始的示例,Mohamed本可以不用请求外部dtd
而直接读取文件并回传至自己的服务器
但这一步的价值在于,如果成功请求了远程dtd文件,解析后再发出请求,即可证明XXE漏洞存在,而读取/etc/passwd不过是XXE利用方式的一种
实例: Wikiloc XXE
网站提供了一种.gpx文件的上传处
攻击者遵循了.gpx的格式并插入dtd
其他利用
1. DOS
]>
&lol9;
递归,10^8个“lol”
2. RCE
这种情况很少发生,但有些情况下攻击者能够通过XXE执行代码,这主要是由于配置不当/开发内部应用导致的。如果我们足够幸运,并且PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么我们就可以执行如下的命令:
]>
John, Doe
I love XML
Computers
9.99
2018-10-01
&xxe;
响应:
{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...
(待补充)
防御
BeeBox中高级针对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!";
}
对于老版本的PHP,不能使用libxml_disable_entity_loader(true);禁用外部实体引用,这里。login现在是直接在SEESION里面取,不再利用xml进行提交。并且使用了mysqli_real_escape_string()函数对$secret进行了特殊字符转义。
禁用外部实体引用:
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中有自定义DTD
Reference
Web Hacking 101
XXE漏洞利用技巧:从XML到远程代码执行
[Web安全] XXE漏洞攻防学习(中)