作者:深信服千里目实验室
原文链接:https://mp.weixin.qq.com/s/WtT0o2ygGGQek3wvIulfiQ
XStream是Java类库,用来将对象序列化成XML(JSON)或反序列化为对象。XStream在运行时使用Java反射机制对要进行序列化的对象树的结构进行探索,并不需要对对象作出修改。XStream可以序列化内部字段,包括私private和final字段,并且支持非公开类以及内部类。在缺省情况下,XStream不需要配置映射关系,对象和字段将映射为同名XML元素。但是当对象和字段名与XML中的元素名不同时,XStream支持指定别名。XStream支持以方法调用的方式,或是Java标注的方式指定别名。XStream在进行数据类型转换时,使用系统缺省的类型转换器。同时,也支持用户自定义的类型转换器。
XStream类图:
漏洞名称 | 漏洞ID | 影响版本 | CVSS |
---|---|---|---|
XStream 远程代码执行漏洞 | CVE-2013-7285 | XStream <= 1.4.6 | 9.8 |
XStream 远程代码执行漏洞 | CVE-2019-10173 | XStream < 1.4.10 | 9.8 |
XStream 远程代码执行漏洞 | CVE-2020-26217 | XStream <= 1.4.13 | 8.0 |
XStream 外部实体注入漏洞 | CVE-2016-3674 | XStream <= 1.4.8 | 7.5 |
XStream 拒绝服务攻击 | CVE-2017-7957 | XStream <= 1.4.9 | 7.5 |
XStream组件漏洞主要是java反序列化造成的远程代码执行漏洞,目前官方通过黑名单的方式对java反序列化攻击进行防御,由于黑名单防御机制存在被绕过的风险,因此以后可能会再次出现类似上述java反序列化漏洞。
根据XStream组件的漏洞,结合XStream常用的使用场景,得到如下风险梳理的场景图。
基于风险梳理思维导图,总结出一种漏洞的利用场景。
XStream远程代码执行漏洞单独使用,即可完成GetShell。一般情况,如果一个Web应用中使用了受漏洞影响版本的XStream,都会受XStream本身的漏洞影响。除此之外,由于XStream是将XML格式数据反序列化成对象。因此如果Web应用还引入了其他的在反序列化过程中容易出现安全问题的依赖,也会出现反序列化漏洞。
从高危漏洞列表中,针对部分近年高可利用漏洞进行漏洞深入分析。
技术背景
java动态代理
Java标准库提供了一种 动态代理(Dynamic Proxy) 的机制:可以在运行期动态创建某个interface的实例。
例子: 我们先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
package test3_proxyclass;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心;
InvocationHandler接口: proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
newProxyInstance: 创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:
loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
getInvocationHandler: 返回指定代理实例的调用处理程序
getProxyClass: 给定类加载器和接口数组的代理类的java.lang.Class对象。
isProxyClass: 当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
newProxyInstance: 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
1 漏洞信息
1.1 漏洞简介
1.2 漏洞概述
包含类型信息的流在unmarshalling
时,会再次创建之前写入的对象。因此XStream会基于这些类型信息创建新的实例。攻击者可以操控XML数据,将恶意命令注入在在可以执行任意shell命令的对象中,实现漏洞的利用。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
XStream <= 1.4.6
1.5 漏洞修复
获取XStream最新版本,下载链接:https://x-stream.github.io/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 环境搭建
基于Windows平台,使用环境
目录下的xstreamdemo
环境,拷贝后使用Idea打开xstreamdemo
文件夹,下载maven资源,运行DemoApplication类,即可启动环境。效果如图。
2.4 漏洞复现
运行sniper
工具箱,填写表单信息,点击Attack,效果如图。
3.漏洞分析
3.1 详细分析
3.1.1 代码分析
传入的payload首先会在com.thoughtworks.xstream.XStream
的fromXML()
方法中处理,在进入unmarshal()
方法中进行解集。
在com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy
类中的unmarshal()
方法中调用start()
方法进行Java对象转换。
在com.thoughtworks.xstream.core.TreeUnmarshaller
类中的start()
方法通过调用readClassType()
获取type
类型。
在readClassType()
方法中调用readClassAttribute
方法。
进入readClassAttribute
方法调用aliasForSystemAttribute
方法获取别名。调用getAttribute
方法,获取reader对象中记录的外部传入XML数据中是否存在对应的标签,如果不存在则返回null。
回到HierarchicalStreams#readClassType
方法中调用realClass
方法,通过别名在wrapped对象中的Mapper中循环查找,获取与别名对应的类。
找到sorted-set
别名对应的java.util.SortedSet
类,并将类存入realClassCache对象中。
回到TreeUnmarshaller#start
方法,调用convertAnother
方法。进入convertAnother
方法后,调用defaultImplementationOf
方法,在mapper对象中寻找java.util.SortedSet接口类的实现类java.util.TreeSet
。
获取java.util.TreeSet
类型,调用lookupConverterForType
方法,寻找对应类型的转换器。进入lookupConverterForType
方法,循环获取转换器列表中的转换器,调用转换器类中的canConvert
方法判断选出的转换器是否可以对传入的type类型进行转换。
转换器TreeSetConverter
父类CollectionConverter
中canConvert
方法判断,传入的type与java.util.TreeMap
相同,返回true,表示可以使用TreeSetConverter
转换器进行转换。
回到DefaultConverterLookup#lookupConverterForType
方法,将选取的converter与对应的type存入typeToConverterMap。
回到TreeUnmarshaller#convertAnother
方法中,调用this.convert
方法。
首先判断传入的xml数据中是否存在reference标签,如果不存在,则将当前标签压入parentStack栈中,并调用父类的convert
方法。
进入convert
方法中,调用转换器中的unmarshal
方法,对传入的xml数据继续解组。
首先调用unmarshalComparator
方法判断是否存在comparator,如果不存在,则返回NullComparator对象。
根据unmarshalledComparator对象状态,为possibleResult对象赋予TreeSet类型对象。
由于possibleResult是一个空的TreeMap,因此最终treeMap也是一个空对象,从而调用treeMapConverter.populateTreeMap
方法。
进入populateTreeMap
方法中,首先调用调用putCurrentEntryIntoMap
方法解析第一个标签,再调用populateMap
方法处理之后的标签(此流程中二级标签只存在一个,因此在处理二级标签时暂不进入populateMap方法)。
具体调用流程如下,com.thoughtworks.xstream.converters.collections.TreeSetConverter
类中调用putCurrentEntryIntoMap
方法 -> com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.readItem()
中的 readClassType()
方法获取传入xml数据中标签名(别名)对应的类(与本节中获取sorted-set对应类的流程相同)。本次获取的是dynamic-proxy对应的java.lang.reflect.Proxy.DynamicProxyMapper
类型,并将别名与类型作为键值对,存入realClassCache中。
回到AbstractCollectionConverter.readItem()
方法中,调用convertAnother
方法,寻找DynamicProxyMapper
对应的convert,获取到DynamicProxyConverter
转换器。
得到com.thoughtworks.xstream.mapper.DynamicProxyMapper$DynamicProxy
,按照之前获取转换器之后的流程,调用转换器中的unmarshal()
方法获取interface
元素,得到java.lang.Comparable
,并添加到mapper中。
在通过循环查询,继续查找下面的节点元素,进而获得了handler java.beans.EventHandler
。
调用Proxy.newProxyInstance
方法创建动态代理,实现java.lang.Comparable接口。
调用convertAnother
方法获取传入type的转换器,java.beans.EventHandler
对应的convert是ReflectionConverter
。并将父类及其对象写进HashMap中
在com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.duUnmarshal()
方法获取下面的节点元素target java.lang.ProcessBuilder
。
具体流程如下:调用getFieldOrNull
方法,判断xml格式中传入的标签名在目标类中是否存在对应属性。
在调用reader.getNodeName()
方法获取标签名,并赋值给originalNodeName。
调用realMember
方法获取反序列化属性的名称。
调用readClassAttribute
方法获取target标签中传入的类名
调用realClass
获取上述过程中类名对应的类,并调用unmarshallField
方法进行解析。
进入方法中,寻找对应type的转换器,由于是java.beans.EventHandler作为动态代理的实现类,所以选择的转化器都是ReflectionConverter
。使用选中的转换器进行解组。
使用ReflectionConverter
convert处理java.lang.ProcessBuilder
,在duUnmarshal()
方法获取command
标签和comand标签下的String标签及其参数。(其中String标签下的参数是在下一层convert调用中获取的。)
调用this.reflectionProvider.writeField
方法,将参数值传入对象中。
在按照获取target标签相同的流程获取action标签,最终将start方法存入对象中。
回到TreeMapConverter#populateTreeMap
方法中,上述过程中构造的object保存在sortedMap中。且其中的动态代理实现的接口是java.lang.Comparable
,因此只要调用java.lang.Comparable
接口中的compareTo
方法,即可触发动态代理,进入java.beans.EventHandler
实现类中的invoke
方法。在populateTreeMap
方法中调用putAll
方法,将sortedMap中的对象写入result变量的过程中会调用到compareTo
,调用链如下。
进入java.beans.EventHandler#invoke
方法中,通过反射执行对象中的方法。
3.1.2补丁分析
XStream1.4.7版本中,在com.thoughtworks.xstream.converters.reflection.ReflectionConverter
添加type != eventHandlerType
阻止ReflectionConverter
解析java.beans.EventHandler
类。从而防御了此漏洞。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
包含类型信息的流在unmarshalling
时,会再次创建之前写入的对象。因此XStream会基于这些类型信息创建新的实例。攻击者可以操控XML数据,将恶意命令注入在在可以执行任意shell命令的对象中,实现漏洞的利用。
1.3 漏洞利用条件
无
1.4 漏洞影响
影响版本:
XStream = 1.4.10
1.5 漏洞修复
获取XStream最新版本,下载链接:https://x-stream.github.io/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 环境搭建
基于Windows平台,使用环境
目录下的xstreamdemo
环境,拷贝后使用Idea打开xstreamdemo
文件夹,下载maven资源,运行DemoApplication类,即可启动环境。效果如图。
2.4 漏洞复现
运行sniper
工具箱,填写表单信息,点击Attack,效果如图。
3.漏洞分析
3.1 详细分析
3.1.1 代码分析
CVE-2019-10173漏洞与CVE-2013-7285漏洞原理相同,由于在XStream的安全模式默认不启动,导致防御失效。
Xstream 1.4.7对于漏洞的防御措施:
通过在com.thoughtworks.xstream.converters.reflection.ReflectionConverter
添加type != eventHandlerType
阻止ReflectionConverter
解析java.beans.EventHandler
类
Xstream 1.4.10漏洞产生原因:
在com.thoughtworks.xstream.converters.reflection.ReflectionConverter
类中,canConvert方法中的type != eventHandlerType
被删除了,使得原来的漏洞利用方式可以再次被利用。
由于在Xstream1.4.10中的com.thoughtworks.xstream.XStream
类增加了setupDefaultSecurity()
方法和InternalBlackList
转换器,通过黑名单的形式对漏洞进行防御。但是安全模式默认不开启,必须在初始化后才可以使用,eg:XStream.setupDefaultSecurity(xStream)
。导致防御失效,造成漏洞的第二次出现。
3.1.2补丁分析
XStream1.4.11版本中,在com.thoughtworks.xstream.XStream
更改安全模式初始化方法中的securityInitialized
标志位。在调用InternalBlackList
转换器中的canConvert
方法时,可以进行黑名单匹配,从而防御了此漏洞。
漏洞防御
在Xstream1.4.11中的com.thoughtworks.xstream.XStream
类中InternalBlackList
类会对java.beans.EventHandler
进行过滤,java.beans.EventHandler
执行marshal
或者unmarshal
方法时,会抛出异常终止程序。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
包含类型信息的流在unmarshalling
时,会再次创建之前写入的对象。因此XStream会基于这些类型信息创建新的实例。攻击者可以操控XML数据,将恶意命令注入在在可以执行任意shell命令的对象中,实现漏洞的利用。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
XStream = 1.4.13
1.5 漏洞修复
获取XStream最新版本,下载链接:https://x-stream.github.io/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 环境搭建
基于Windows平台,使用环境
目录下的xstreamdemo
环境,拷贝后使用Idea打开xstreamdemo
文件夹,下载maven资源,运行DemoApplication类,即可启动环境。效果如图。
2.4 漏洞复现
运行sniper
工具箱,填写表单信息,点击Attack,效果如图。
3.漏洞分析
3.1 详细分析
3.1.1 代码分析
代码分析:传入的payload首先会在com.thoughtworks.xstream.XStream
的fromXML()
方法中处理,在进入unmarshal()
方法中进行解集。
在com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy
类中的unmarshal()
方法中调用start()
方法进行Java对象转换。
在com.thoughtworks.xstream.core.TreeUnmarshaller
类中的start()
方法通过调用readClassType()
获取type
类型。
在readClassType()
方法中调用readClassAttribute
方法。
进入readClassAttribute
方法调用aliasForSystemAttribute
方法获取别名。调用getAttribute
方法,获取reader对象中记录的外部传入XML数据中是否存在对应的标签,如果不存在则返回null。
回到HierarchicalStreams#readClassType
方法中调用realClass
方法,通过别名在wrapped对象中的Mapper中循环查找,获取与别名对应的类。
在DefaultMapper
中,通过反射,获取到string标签传入的class,并将类存入realClassCache对象中。
回到TreeUnmarshaller#start
方法,调用convertAnother
方法。进入convertAnother
方法后,调用lookupConverterForType
方法,寻找对应类型的转换器。进入lookupConverterForType
方法,循环获取转换器列表中的转换器,调用转换器类中的canConvert
方法判断选出的转换器是否可以对传入的type类型进行转换。
转换器ReflectionConverter
中canConvert
方法判断,传入的type非null,返回true,表示可以使用ReflectionConverter
转换器进行转换。
回到DefaultConverterLookup#lookupConverterForType
方法,将选取的converter与对应的type存入typeToConverterMap。
回到TreeUnmarshaller#convertAnother
方法中,调用this.convert
方法。
首先判断传入的xml数据中是否存在reference标签,如果不存在,则将当前标签压入parentStack栈中,并调用父类的convert
方法。
在com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.duUnmarshal()
方法获取下面的节点元素iter java.util.ArrayList$Itr
。
具体流程如下:调用getFieldOrNull
方法,判断xml格式中传入的标签名在目标类中是否存在对应属性。
在调用reader.getNodeName()
方法获取标签名,并赋值给originalNodeName。
调用realMember
方法获取反序列化属性的名称。
调用readClassAttribute
方法获取iter标签中传入的类名
调用realClass
获取上述过程中类名对应的类,并调用unmarshallField
方法进行解析。
进入方法中,寻找对应type的转换器,使用选中的ReflectionConverter转换器进行解组。
使用ReflectionConverter
convert处理java.util.ArrayList$Itr
,在duUnmarshal()
方法获取cursor
标签和cursor
标签下的参数。(调用unmarshallField
方法,与上述流程相似)
调用this.reflectionProvider.writeField
方法,将参数值传入对象中。
回到AbstractReflectionConverter#doUnmarshal
方法中获取后续的标签及其参数(分别为lastRet,expectedModCount,outer-class)。
按照同样的反序列化流程获取属性值,并写入对象。
解析outer-class标签,由于type是java.util.ArrayList
,选择转换器是CollectionConverter
。
调用CollectionConverter#unmarshal
方法进行反序列化。
调用CollectionConverter#populateCollection
-> CollectionConverter#addCurrentElementToCollection
->AbstractCollectionConverter#readItem
方法。最终调用realClass方法获取type类,获取过程中将outer-class标签下的子标签存入realClassCache中。
回到AbstractCollectionConverter#readBareItem
方法调用convertAnother
方法,按照之前的流程进行反序列化,为属性赋值,并写入对象。
最终返回ProcessBuilder
对象,写入FilterIterator
对象中。
在按照获取java.util.ArrayList$Itr
对象相同的流程获取javax.imageio.ImageIO$ContainsFilter
对象,通过反序列化为其内部的method属性和name属性进行赋值。
在选择转换器的过程中,由于method属性的类型是java.lang.reflect.Method
,因此选择对应的转换器为JavaMethodConverter。
调用JavaMethodConverter#unmarshal
方法进行xml数据解析,获取java.lang.processBuilder类中的start方法对象,写入到javax.imageio.ImageIO$ContainsFilter
对象中。
再按照相同的流程,将start方法名写入name属性中。
最终在调用ReflectionProvider#writeField
方法将javax.imageio.ImageIO$ContainsFilter
对象写进FilterIterator
对象的filter属性中。
将FilterIterator
对象返回给最初的iterator
对象中。
调用iterator.next()
方法时,会调用其实现类FilterIterator
中的next方法。
进入调用advance方法,调用filter方法时,会通过反射执行ProcessBuilder对象中的start方法,从而造成代码执行。
3.1.2补丁分析
XStream1.4.11版本中,在com.thoughtworks.xstream.XStream
更改安全模式初始化方法中的securityInitialized
标志位。在调用InternalBlackList
转换器中的canConvert
方法时,可以进行黑名单匹配,从而防御了此漏洞。
漏洞防御
XStream1.4.14版本中,在com.thoughtworks.xstream.XStream
的黑名单添加java.lang.ProcessBuilder
和javax.imageio.ImageIO$ContainsFilter
。从而防御了此漏洞。
漏洞利用视频,请转到原文观看,链接:https://mp.weixin.qq.com/s/WtT0o2ygGGQek3wvIulfiQ
1.https://blog.csdn.net/yaomingyang/article/details/80981004<br>
2.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_6...XSTREAM_1_4_7
3.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_10...XSTREAM_1_4_11
4.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_13...XSTREAM_1_4_14
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1417/