XStream反序列化基础
一、项目环境
启动一个简单的 maven 项目
- jdk8u152
- 导入依赖
1 |
|
二、XStream 源码分析
根据下面的 XStream 的 UML 设计图可以知道 XStream 大致可以分为 5 个部分(参考链接:链接)
- XStream 作为客户端对外提供 XML 解析与转换的相关方法
- AbstractDriver 为XStream提供流解析器和编写器的创建。目前支持XML(DOM,PULL)、JSON解析器。解析器HierarchicalStreamReader,编写器HierarchicalStreamWriter(PS:XStream默认使用了XppDriver)。
- MarshallingStrategy 编组和解组策略的核心接口,两个方法:
marshal:编组对象图
unmarshal:解组对象图
TreeUnmarshaller 树解组程序,调用mapper和Converter把XML转化成java对象,里面的start方法开始解组,convertAnother方法把class转化成java对象。
TreeMarshaller 树编组程序,调用mapper和Converter把java对象转化成XML,里面的start方法开始编组,convertAnother方法把java对象转化成XML。
它的抽象子类AbstractTreeMarshallingStrategy有抽象两个方法
createUnmarshallingContext
createMarshallingContext
用来根据不同的场景创建不同的TreeUnmarshaller子类和TreeMarshaller子类,使用了策略模式,如ReferenceByXPathMarshallingStrategy创建ReferenceByXPathUnmarshaller,ReferenceByIdMarshallingStrategy创建ReferenceByIdUnmarshaller(PS:XStream默认使用ReferenceByXPathMarshallingStrategy)。 - Mapper 映射器,XML的elementName通过mapper获取对应类、成员、属性的class对象。支持解组和编组,所以方法是成对存在real 和serialized,他的子类MapperWrapper作为装饰者,包装了不同类型映射的映射器,如AnnotationMapper,ImplicitCollectionMapper,ClassAliasingMapper。
- ConverterLookup 通过Mapper获取的Class对象后,接着调用lookupConverterForType获取对应Class的转换器,将其转化成对应实例对象。DefaultConverterLookup是该接口的实现类,同时实现了ConverterRegistry的接口,所有DefaultConverterLookup具备查找converter功能和注册converter功能。所有注册的转换器按一定优先级组成由TreeSet保存的有序集合(PS:XStream 默认使用了DefaultConverterLookup)。
2.1 MarshallingStrategy 编码策略
- marshall:object 对象 -> xml 编码
- unmarshall:xml 编码 -> object 对象
两个重要的实现类:
package com.thoughtworks.xstream.core.TreeMarshaller
:树编组程序,调用mapper和Converter把java对象转化成XML,里面的start方法开始编组,convertAnother方法把java对象转化成XML。package com.thoughtworks.xstream.core.TreeUnmarshaller
:树解组程序,调用mapper和Converter把XML转化成java对象,里面的start方法开始解组,convertAnother方法把class转化成java对象。
在start()
方法中调用convertAnother()
方法把 XML 转化为 Java 对象;
后续跟反序列化的过程中在进行详细的分析
2.2 Mapper 映射器
简单来说就是通过 mapper 获取对象对应的类、成员、Field 属性的 Class 对象,赋值给 XML 的标签字段,最终得到序列化后的 XML。
2.3 Converter 转换器
XStream 为 Java 常见的类型提供了 Converter 转换器。转换器注册中心是 XStream 组成的核心部分。
转换器的职责是提供一种策略,用于将对象图中找到的特定类型的对象转换为 XML 或将 XML 转换为对象。简单地说,就是输入 XML 后它能识别其中的标签字段并转换为相应的对象,反之亦然。
转换器需要实现 3 个方法,这三个方法分别是来自于 Converter
类以及它的父类 ConverterMatcher
- canConvert 方法:告诉 XStream 对象,它能够转换的对象;
- marshal 方法:能够将对象转换为 XML 时候的具体操作;
- unmarshal 方法:能够将 XML 转换为对象时的具体操作;
更多转换器可参考:https://x-stream.github.io/converters.html,这里告诉了我们针对各种对象,XStream 都做了哪些支持。
2.4 EventHandler 类(与 XStream 无关)
EventHandler 类为动态生成事件侦听器提供支持,这些侦听器的方法执行一条涉及传入事件对象和目标对象的简单语句。
EventHandler 类是实现了 InvocationHandler 的一个类(动态代理),设计本意是为交互工具提供 beans,建立从用户界面到应用程序逻辑的连接。
EventHandler 类定义的代码如下,其含有 target 和 action 属性,在 EventHandler.invoke()->EventHandler.invokeInternal()->MethodUtil.invoke()
的函数调用链中,会将前面两个属性作为类方法和参数继续反射调用:
1 |
|
2.4 DynamicProxyConverter 动态代理转换器
DynamicProxyConverter 即动态代理转换器,是 XStream 支持的一种转换器,其存在使得 XStream 能够把 XML 内容反序列化转换为动态代理类对象;
XStream 反序列化漏洞的 PoC 都是以 DynamicProxyConverter
这个转换器为基础来编写的。
以官网给的例子为例:
1 |
|
dynamic-proxy 标签在 XStream 反序列化之后会得到一个动态代理类对象,当访问了该对象的com.foo.Blah
或 com.foo.Woo
这两个接口类中声明的方法时(即 interface 标签内指定的接口类),就会调用 handler 标签中的类方法com.foo.MyHandler
三、XStream 序列化和反序列化实践
- 定义接口 IPerson
1 |
|
- 定义 Person 实现接口 IPerson
1 |
|
- XStream 序列化 Serialize.java
XStream 序列化时调用XStream.toXML()
来实现的
1 |
|
XStream 反序列化通过调用XStream.fromXML()
实现,其中获取 XML 文件内容可以通过Scanner()
或FileInputStream
来获取;
- XStream 反序列化 Deserialize.java
1 |
|
四、XStream反序列化流程分析
本人分析完以后发现,这部分内容并不重要,因为 XStream 漏洞产生的原因不是因为 XStream 反序列化本身导致的,而是因为 XStream 支持的一个名为 DynamicProxyConverter
的转换器
4.1 反序列化分析
在 Person 类反序列化调用的fromXML()
下断点进行调试分析其反序列化的过程;
- tmp.xml
1 |
|
F7
跟进可以看到unmarshal()
函数,就是之前我们说将 XML 转换为对象的函数,我们继续跟进unmarshal()
函数,可以看到还是执行一个unmarshal()
函数,继续跟进
可以看到这个函数先进行了一个安全判断,再次之后执行marshallingStrategy
的unmarshal()
函数,继续跟进该函数;
来到了抽象类AbstractTreemarshallingStrategy
,其中调用了start()
方法,我们跟进start()
方法,来到
这个函数就是我们在进行源码分析的时候介绍的反序列化方法,其中 type 是由reader
和mapper
获取中,其中 reader 中包含了我们的类信息,获取的 type 是跟我们反序列化的类的类型相关的数据,进入convertAnother()
进行详细分析;
来到这里我们看到,这个converter
是根据 type 查询到的转换器,根据之前的介绍知道,converter 是将输入 XML 后它能识别其中的标签字段并转换为相应的对象工具;
获取到 converter 后,我们跟进下面的convert()
方法中查看详细的转换过程;
一路F8
执行来到这里进入父类的convert()
;
又看到一个unmarshal
方法,肯定跟反序列化有关,继续跟进;跟进后一路F8
来到这里对其中的属性进行反序列化
这里存在两个方法,第一个方法instantiateNewInstance(reader, context);
创建了一个反序列化的对象实例;然后doUnmarshal(result, reader, context);
对对象实例中的属性进行意义反序列化赋值;
F7
跟进这个函数查看具体实现;
执行完该函数发现返回了第一个属性的值;具体的反序列化结果和之前的内容相差不大;
在这里将相应的值写入对应的属性中,在执行完毕以后,后面存在一个 while 循环判断是否还有其他的属性(好像是在doUnmarshal
进行属性循环检查的);
在这个 while 循环中一个一个的提取相应属性进行赋值;
4.2 总结
就上述的反序列化的分析流程看来,XStream 的反序列化本身似乎是没有什么问题的;
那么产生 XStream 反序列化漏洞的原因是什么呢?
五、XStream 反序列化漏洞原理
这里先简单讲讲 XStream 反序列化漏洞原理,后续在详细介绍一下如何应用;
XStream 反序列化漏洞的存在是因为 XStream 支持一个名为 DynamicProxyConverter
的转换器,该转换器可以将 XML 中 dynamic-proxy
标签内容转换成动态代理类对象,而当程序调用了 dynamic-proxy
标签内的 interface
标签指向的接口类声明的方法时,就会通过动态代理机制代理访问 dynamic-proxy
标签内 handler
标签指定的类方法。
利用这个机制,攻击者可以构造恶意的XML内容,即 dynamic-proxy
标签内的 handler
标签指向如 EventHandler
类这种可实现任意函数反射调用的恶意类、interface
标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意 XML 内容后即可触发反序列化漏洞、达到任意代码执行的目的。