前言
XStream - About XStream (x-stream.github.io)
XStream是一个简单的库,可以将对象序列化为XML,然后再序列化回来。这是官网给的序列化例子。
XStream的架构由6个主要组件组成。
- Converters
- Mappers
- Drivers (Writer and Reader)
- Context
- Type Permissions
- Facade
Converters用来实现转换为XML的对象时,
XStream附带了许多常见类型的转换器,包括基本类型、字符串、集合、数组、null、Date等。
XStream还有一个默认的转换器,当没有其他转换器匹配某个类型时使用。这使用反射为对象中的所有字段自动生成XML。
Drivers是负责从“流”中直接读取并操作XML,有两个接⼝分别是HierarchicalStreamWriter
和 HierarchicalStreamReader
分别负责将 Java对象序列化到XML数据格式以及从XML数据格式反序列化到Java对象。
Context是序列化和反序列化过程中所必须的⼀个对象,它会根据操作创建MarshallingContext 或 UnmarshallingContext,相关过程将会从对应的Context中查找对应的Converters去完成相应的转换操作。
源码分析
依赖
1 | <dependency> |
一个demo看看Xstream如何将java对象序列化成xml数据
首先是两个简单的pojo类,都实现了Serializable接口并且重写了readObject方法
people.java
1 | package Xstream; |
Company.java
1 | package Xstream; |
test.java
1 | package Xstream; |
这是实现了Serializable接口的结果
未实现Serializable接口
二者结果是不一样的。java反序列化利用链,类必须要可Serializable,但是在Xstream上,并不是必须。
重点看到反序列化的流程上
在test添加代码
1 | People p = (People) xStream.fromXML(xml); |
断点打在fromXML这个方法上
一路经过等等方法
com.thoughtworks.xstream.XStream#fromXML(java.lang.String)
com.thoughtworks.xstream.XStream#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, java.lang.Object, com.thoughtworks.xstream.converters.DataHolder)
到com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy#unmarshal
方法调用了TreeUnmarshaller
类的start方法
来看这个start方法
start()
方法是搞编组的,start()
方法首先调用了 readClassType()
方法,会获取到 XML 文件中根标签的类型
接着调用了convertAnother方法,这里会获取一个converter,Xstream的思路是通过不同的converter来处理序列化数据中不同类型的数据,我们跟进该方法看看在处理最外层的没有实现Serializable接口的People类时用的是哪种converter
在实现了接口的时候返回了SerializableConverter
删掉Serializable接口再来看看,这时候可以看到返回的为ReflectionConverter,处理我们自定义的未实现Serializable接口的People类时使用ReflectionConverter,该Converter的原理是通过反射获取类对象并通过反射为其每个属性进行赋值。
后面就是convert()
方法会调用 getCurrentReferenceKey()
来获取当前的 Reference
键即标签名,接着将当前标签名压入 parentStack 栈中。后面就不分析了。
重新实现Serializable接口,然后在我们重写的redaObject方法打上断点,停了下来,说明重写的方法得到了执行
调用readObject方法就可以进行反序列化攻击了(一种利用方式, CVE-2021-21344就是利用了PriorityQueue类的readObject)。当然java反序列化利用链,类必须要可Serializable,但是在Xstream上,并不是必须。
漏洞前瞻
官网可以看到历史漏洞XStream - Security Aspects (x-stream.github.io),官方会把poc直接给出来
打算复现一下漏洞
CVE ID | Description&&Restrictions |
---|---|
CVE-2021-39149 | 无限制 |
CVE-2020-26258 | 服务端请求伪造漏洞 |
CVE-2020-26258
poc
1 | <map> |
调试
要把这个xml反序列化
1 | package Xstream; |
在com.thoughtworks.xstream.core.TreeUnmarshaller#convert
方法打上断点,前面的过程就是上面分析的。
步入
上面那行调用了createCollection方法根据XML数据格式中的反射创建java.util.Map对象。
在populateMap方法中反序列化Map中的KV对。
在com.thoughtworks.xstream.mapper.DefaultMapper#realClass方法打个断点
简单看看获取KV对的调用栈吧。现在已经通过Java反射完成初始化。
然后就是调用HashMap.put() ,熟悉吧
步入
步入
步入
步入
步入到getInputStream方法,然后就看到
EXP
自己参考构造的喔,不是完全照搬。
1 | package Xstream; |
不仅仅可以使用Unsafe去实例化一个没有构造⽅法或者构造⽅法为private修饰的类,使用反射中的newConstructorForSerialization也可以。
1 | /* |
那么又有一个问题,为什么要实例化这样一个类并且获取对象呢?
好像xml写的已经很清除了,好像也是反序列化执行恶意行为的必经之路吧。
CVE-2021-39149
测试版本1.4.7
通过TemplatesImpl加载字节码实现RCE
poc分析
1 | <linked-hash-set> |
官网也给出了一个poc但是放到idea里面会报错但是执行反序列化还是会弹计算器,好悬啊= =,上面那个poc是参考backcover7发的文章。在官网的poc基础上改两个地方
- proxy后面的
>
前的/
删掉不需要自闭合 - 在TemplatesImpl结束下添加
<boolean>false</boolean>
当然不能只跑一下paylaod然后调试一下,这样走马观花得学习是不够得,需要的是学习如何通过xStream.toXML()
来输出xml,培养构造EXP的能力。
调用链根据xml的嵌套结构很容易可以得到
1 | java.util.LinkedHashSet |
EXP构造
首先是三个工具方法,都是通过反射来操作对象。
1 | public static void setFieldValue(Object obj, String fieldName, Object |
setFieldValue
很熟悉了就不分析了。
instaniateUnsafe
,嗯,Unsafe,之前见过一次,在构造LazyList利用CC的时候。用来将一个对象的内部字段修改的我们构造的恶意类的。这个方法就是通过反射获取Unsafe,这也是这个对象名为实例化Unsafe的原因。
setField
这个方法使用了SunUnsafeReflectionProvider
类,注意他是从Xstream的1.4.7版本以来才有的。
使用的两个方法:getFieldOrNull
返回在某个类中定义的字段。writeField
修改类中定义的字段。
然后通过sun.tracing.dtrace.DTraceProbe
的uncheckedTrigger
方法可以实现调用getOutputProperties方法来加载恶意字节码。
1 | TemplatesImpl templates = new TemplatesImpl(); |
sun.tracing.NullProvider
接受一个类型为 Class<? extends Provider>
的参数,并用该参数调用其超类 (ProviderSkeleton
) 的构造函数。
ProviderSkeleton的probes是一个HashMap,所以往sun.tracing.NullProvider的probes放一个可以触发hashcode的hashmap,并且将之前危险代码放在value部分
故构造为
1 | HashMap map = new HashMap(); |
使用动态代理,invoke
方法在动态代理当中会自动执行。HashMap设置为被代理类,通过设置Field值就会调用
1 | InvocationHandler handler = (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl")); |
然后设置值将链子首位相接
1 | setFieldValue(handler, "classToInvocationHandler", new LinkedHashMap()); |
通过设置classToInvocationHandler
为new LinkedHashMap()
。
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
类的invoke方法执行,defaultHandler设置为nullProvider,就会调用sun.tracing.ProviderSkeleton#invoke
通过反射可以将method
与proxy
从LinkedHashMap
中取出来这样就可以调用map的put方法。
完整EXP
1 | package Xstream; |
调用堆栈
1 | start:1007, ProcessBuilder (java.lang) |
小结
在EXP中new对象是为了直接调用方法,来实现连接
Unsafe通过设置Filed的值来在反序列化是被动调用方法来实现连接
使用动态代理是为了调用com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl的invoke方法进而调用sun.tracing.ProviderSkeleton#invoke
好难啊,这链子真的是人想出来的吗?
后记
先学习佬如何分析poc然后构造exp,然后自己又尝试根据poc来构造exp。
Xstream之所以有这么多可以利用的类还是因为之前分析的反序列化的类无需实现Serializable接口,这是与JDK原生反序列化所不同的地方。
1 | TreeMapConverter->TreeMap#putAll->TreeMap#put->source#compareTo->...->sink |
看xml文件用Unsafe类构造exp还是比较容易的
JSON反序列化
Xstream也可以转换为JSON数据,就是多了一层转换也是会触发反序列化的,本质还是Xstream反序列化。
XStream针对JSON格式的数据的处理有两个driver可以提供支持,分别是JsonHierarchicalStreamDriver
和JettisonMappedXmlDriver
。
Serialization (Java Object -> JSON) | Deserialization (JSON -> Java Object) | |
---|---|---|
JsonHierarchicalStreamDriver | √ | × |
JettisonMappedXmlDriver | √ | √ |
需要引入jettison
依赖
1 | <dependency> |
1 | XStream xstream = new XStream(new JettisonMappedXmlDriver()); |
参考:
XStream反序列化详解(一) | m0d9’s blog
XStream反序列化详解(二) | m0d9’s blog
Xstream反序列化分析(CVE-2021-39149) - 先知社区 (aliyun.com)
Xstream反序列化远程代码执行漏洞深入分析 | 天融信阿尔法实验室 (topsec.com.cn)