方便永远是安全的敌人
你的知识面,决定你的攻击面
1简述
XXE(XML External Entity)是指xml外部实体攻击漏洞。XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。当包含对外部实体的引用的XML输入被弱配置XML解析器处理时,就会发生这种攻击。这种攻击通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
而如今越来越多的WEB程序被发现和报告存在XXE漏洞,比如说facebook,paypal等等。很多XML的解析器默认是含有XXE漏洞的,这意味着开发人员有责任确保这些程序不受此漏洞的影响。尽管XXE漏洞已经存在了很多年,但是他从来没有获得他应得的关注度。究其原因一方面是对XXE这种利用难度高的漏洞不够重视,另一方面是XML的存在对互联网的广泛影响,以至于他出现任何问题时牵扯涉及到的应用、文档、协议、图像等等都需要做相应的更改。
2 XML结构介绍
要了解XXE漏洞,那么一定要先学习一下有关XML的基础知识。
XML是一种非常流行的标记语言,在1990年代后期首次标准化,并被无数的软件项目所采用。它用于配置文件,文档格式(如OOXML,ODF,PDF,RSS,...),图像格式(SVG,EXIF标题)和网络协议(WebDAV,CalDAV,XMLRPC,SOAP,XMPP,SAML, XACML,...),他应用的如此的普遍以至于他出现的任何问题都会带来灾难性的结果。
XML被设计为传输和存储数据,其焦点是数据的内容,目的是把数据从HTML分离,是独立于软件和硬件的信息传输工具。XML文档有自己的一个格式规范,这个格式规范是由一个叫做DTD(document type definition)的东西控制的,如下:
上面这个DTD定义了XML的根元素是message,然后元素下面还有一些子元素,其中DOCTYPE是DTD的声明;ENTITY是实体的声明,所谓实体可以理解为变量;SYSTEM、PUBLIC是外部资源的申请。那么XML到时候必须像如下这么写:
现在我们了解了XML的实体的定义,但是还没有对实体进行分类,从两个角度可以把XML分为两类共4个类型:(内部实体、外部实体)、(通用实体、参数实体)。其中两大类含有重复的地方。
内部实体:
(DTD定义代码)
(引用代码)
使用&xxe对上面定义的xxe实体进行了引用,到时候输出的时候&xxe就会被“test”替换。而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。
外部实体:
外部实体表示外部文件的内容,用 SYSTEM 关键词表示,通常使用文件名”>或者public_ID” “文件名”>的形式引用外部实体。
有些XML文档包含system标识符定义的“实体”,这些文档会在DOCTYPE头部标签中呈现。这些定义的’实体’能够访问本地或者远程的内容。假如 SYSTEM 后面的内容可以被用户控制,那么用户就可以随意替换为其他内容,从而读取服务器本地文件(file:///etc/passwd)或者远程文件(http://www.baidu.com/abc.txt)。
通用实体:
用&实体名;引用的实体,他在DTD中定义,在XML文档中引用
参数实体:
a.使用 % 实体名(这里空格不能少)在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
b.只有在DTD文件中,参数实体的声明才能引用其他实体
c.和通用实体一样,参数实体也可以外部引用
3普通的XML注入
在介绍XXE之前,先简单说一下普通的XML注入,为什么要提XML注入呢,我们从XXE的全称(XML外部实体注入)可以看出,XXE也是一种XML注入,只不过注入的是XML外部实体罢了。
从上图可以看出来,所谓的XML注入就是在XML中用户输入的地方,根据输入位置上下文的标签情况,插入自己的XML代码。虽然由于普通的XML注入利用面窄,现实中几乎用不到,但是我们可以想到,既然可以插入XML代码我们为什么不能插入XML外部实体呢,如果能注入成功并且成功解析的话,就会大大扩宽我的XML注入的攻击面了。于是就出现了XXE
4从回显中读取本地敏感文件(Normal XXE)
这个攻击场景模拟的是服务端能接收并解析XML格式的输入并且有回显的时候,我们可以输入我们自定义的XML代码,通过外部引用实体的办法,引用服务器上面的文件。
先给出服务器端解析XML的php代码
xxe.php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>
其中libxml_disable_entity_loader(BOOL)函数接收true或false两种布尔型参数,用来表示是否允许禁用外部加载实体,当值为false时允许加载外部实体;
通过file_get_contents()加载传入的参数,再通过DOMDocument类中的loadXML函数加载外部传入的实体(XML),最后将结果返回显示
因此我们构造一个XML外部实体,用来访问服务器上的敏感文件,然后再数据传输过程中将自己的实体注入。
Payload:
]>
&xxe;
结果如下:
当然,回显注入属于是例外中的例外,毕竟XML本身就不是输出用的,一般都是用于配置或者某些极端情况下利用其它漏洞能恰好实例化解析XML的类,因此想要利用现实中更真实的XXE漏洞需要寻找一个不依靠回显的方法-----外带
5无回显读取本地敏感文件(Blind OOB XXE)
想要外带就必须能发起请求,那么什么地方能发起请求呢?很明显就是我们的外部实体定义的时候,其实光发起请求还不行,我们还得能把我们的数据传出去,而我们的数据本身也是一个对外的请求,也就是说,我们需要在请求中引用另一次请求的结果,分析下来只有我们的参数实体能做到了(并且根据规范,我们必须在一个 DTD 文件中才能完成“请求中引用另一次请求的结果”的要求)。照例给出存在问题的服务端代码。
xxe.php:
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>
可以看到这个服务器配置与上一个相比取消了回显,但是依然是不安全的。
Payload:
%remote;%int;%send;
]>
再提交数据的时候引用另一个DTD文件,test.dtd将服务器上的敏感文件进行base64编码后转发给攻击者ip:9999端口上
test.dtd:
">
结果如下:
我们从 payload 中能看到 连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 test.dtd ,有点类似于将 test.dtd 包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程主机上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。
进一步对XXE漏洞分析后,我们可以很清晰地看到我们实际上都是通过file协议读取本地文件,或者通过http协议发出请求,类比一下其他漏洞例如SSRF,发现这两种漏洞的利用方式非常相似,因为他们都是从服务器向另一台服务器发起请求,所以想要更进一步的利用XXE漏洞我们要清楚在何种平台可以使用何种协议
6XXE的真实案例(微信支付XXE)
在2018年7月4日微信SDK爆出XXE漏洞,通过该漏洞,攻击者可以获取服务器中目录结构、文件内容,如代码、各种私钥等。获取这些信息以后,攻击者便可以为所欲为。
漏洞描述:
微信支付提供了一个接口,供商家接收异步支付结果,微信支付所用的java sdk在处理结果时可能触发一个XXE漏洞,攻击者可以向这个接口发送构造恶意payloads,获取商家服务器上的任何信息,一旦攻击者获得了敏感的数据 (md5-key and merchant-Id etc.),他可能通过发送伪造的信息不用花钱就购买商家任意物品
漏洞产生原因:
微信支付SDK的XXE漏洞产生原因都是因为使用了DocumentBuilderFactory没有限制外部查询而导致XXE
漏洞实例代码:
简单的代码审计一下可以发现,在构建了documentBuilder以后对传进来的strXML直接进行解析,并没有任何过滤检查的步骤,而且悲剧的是此处传入的参数是攻击者可控的注入点,于是就出现了XXE漏洞。
简化版:
完整代码:
攻击代码:
直接向其中注入XML外部实体,返回c盘上的系统配置文件内容,由于可以直接回显所以采用了最简单的回显注入而不是外带注入
7XXE漏洞防御
1.使用开发语言提供的禁用外部实体的方法
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))
2.过滤用户提交的XML数据
过滤关键词:!ENTITY,或者SYSTEM和PUBLIC
3.使用第三方应用代码及时升级补丁