XStream反序列化
XStream简介
XStream是一个简单的基于Java库,Java对象序列化到XML,反之亦然(即:可以轻易的将Java对象和xml文档相互转换)。
反序列化基本原理
XStream实现了一套序列化和反序列化机制,核心是通过Converter转换器来将XML和对象之间进行相互的转换,XStream反序列化漏洞的存在是因为XStream支持一个名为DynamicProxyConverter的转换器,该转换器可以将XML中dynamic-proxy标签内容转换成动态代理类对象,而当程序调用了dynamic-proxy标签内的interface标签指向的接口类声明的方法时,就会通过动态代理机制代理访问dynamic-proxy标签内handler标签指定的类方法;利用这个机制,攻击者可以构造恶意的XML内容,即dynamic-proxy标签内的handler标签指向如EventHandler类这种可实现任意函数反射调用的恶意类、interface标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意XML内容后即可触发反序列化漏洞、达到任意代码执行的目的。
当然在这之前简单介绍几个重要的小知识
EventHandler类
EventHandler类是实现了InvocationHandler的一个类,设计本意是为交互工具提供beans,建立从用户界面到应用程序逻辑的连接
EventHandler类定义的代码如下,其含有target和action属性,在EventHandler.invoke()->EventHandler.invokeInternal()->MethodUtil.invoke()的函数调用链中,会将前面两个属性作为类方法和参数继续反射调用
1 | public static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException { |
Converter转换器
XStream为Java常见的类型提供了Converter转换器。转换器注册中心是XStream组成的核心部分。
转换器的职责是提供一种策略,用于将对象图中找到的特定类型的对象转换为XML或将XML转换为对象。
简单地说,就是输入XML后它能识别其中的标签字段并转换为相应的对象,反之亦然。
转换器需要实现3个方法:
- canConvert方法:告诉XStream对象,它能够转换的对象;
- marshal方法:能够将对象转换为XML时候的具体操作;
- unmarshal方法:能够将XML转换为对象时的具体操作;
具体参考:http://x-stream.github.io/converters.html
POC分析
当然基本的demo还是要给一个
1 | public class Test { |
1.sorted-set
影响版本
1.4.5,1.4.6,1.4.10
分析
经典调用计算器
1 | <sorted-set> |
现在我们来对流程进行追踪,在AbstractTreeMarshallingStrategy.unmarshal()函数中,调用了TreeUnmarshaller.start()函数,即开始解析XML
我们直接从com.thoughtworks.xstream.core.TreeUnmarshaller#start开始
发现会调用HierarchicalStreams.readClassType()来获取到PoC XML中根标签的类类型
最终在com.thoughtworks.xstream.mapper.ClassAliasingMapper#realClass找到了java.util.SortedSet
接着是调用convertAnother()函数对java.util.SortedSet类型进行转换,我们跟进去该函数,其中调用mapper.defaultImplementationOf()函数来寻找java.util.SortedSet类型的默认实现类型进行替换,这里转换为了java.util.TreeSet类型
接着看到调用converterLookup.lookupConverterForType()来寻找TreeSet对应类型的转换器,可以看到这里的逻辑是,迭代this.converters,直到找到能转换出TreeSet类型
往下调试,在AbstractReferenceUnmarshaller.convert()函数中看到,会调用getCurrentReferenceKey()来获取当前的Reference键即标签名,接着将当前标签名压入parentStack栈中
之后调用其父类即的FastStack.convert()方法,跟进去,显示将类型压入栈,然后调用转换器TreeSetConverter的unmarshal()方法
往下调试,his.treeMapConverter.populateTreeMap()看英文名就能知道是填充TreeMap,跟进这里先判断是否是第一个元素,是的话就调用putCurrentEntryIntoMap()函数,即将当前内容填充到Map中
跟进去,发现调用readItem()函数读取标签内的内容并缓存到target这个Map中
跟入,一直到com.thoughtworks.xstream.mapper.CachingMapper#realClass,现在他就会去寻找这个dynamic-proxy所对应的类
最后找到这个类
接下来回到com.thoughtworks.xstream.core.TreeUnmarshaller#convertAnother(java.lang.Object, java.lang.Class, com.thoughtworks.xstream.converters.Converter),找到这个动态代理类的转换器
接下来还是调用getCurrentReferenceKey()来获取当前的Reference键即标签名,接着将当前标签名压入parentStack栈中
之后一直到这里最重要的部分com.thoughtworks.xstream.converters.extended.DynamicProxyConverter#unmarshal,这里按标签内容生成对应接口的动态代理,此时这个DUMMY是一个空的代理实现
继续往下执行handler = (InvocationHandler)context.convertAnother(proxy, handlerType);,接下来转换器转换最终得到EventHandler
接下来替换代理
之后再回到之前的com.thoughtworks.xstream.converters.collections.TreeMapConverter#populateTreeMap,这里会把结果把存到result
最终
调用到java.beans.EventHandler#invokeInternal,之后用反射调用ProcessBuilder的start方法触发命令执行
其他说明
在小于等于1.3.1版本,运行报错显示TreeMap没有包含comparator元素,即不支持PoC中两个子标签元素调用compareTo()进行比较,因此无法利用
在1.4-1.4.5版本无法触发的原因
在TreeSetConverter.unmarshal()中,只有当sortedMapField和treeMap不为null时,才能进入populateTreeMap()
而在1.4-1.4.4版本中,sortedMapField默认为null,因此无法成功利用,这里以1.4.4版本为例
在1.4.7-1.4.9版本中,ReflectionConverter.canConvert()函数中添加了对EventHandler类的过滤
2.tree-map
适用范围
版本<=1.4.6或=1.4.10
分析
和sorted-map差不多,直接给payload
1 | <tree-map> |
其他说明
唯一与sorted-set有点区别的地方就是,在com.thoughtworks.xstream.converters.collections.TreeMapConverter#unmarshal,可以看到没有TreeSetConverter那么多的限制
在<=1.3.1版本的当中
会报错显示TreeMap没有包含comparator元素,即不支持PoC中两个子标签元素调用compareTo()进行比较,因此无法利用
在1.4.7-1.4.9版本,ReflectionConverter.canConvert()函数中添加了对EventHandler类的过滤