剖析反序列化原理基本操作
一、XStream 框架组成分析
XStream 是 java 实现对 javaBean(实用类)简单快速进行序列化反序列化的框架。目前支持 XML 或 JSON 格式数据的序列化或反序列化过程。
XStream 总体主要由上图所示的五个接口和抽象类组成。其中,
AbsractDriver 是为 XStream 提供解析器和编辑器的创建的抽象类。XStream 默认使用的解析器是 XppDriver(这也就解释为了什么 XStream 使用默认的构造方法创建 XStream 对象的时候,需要依赖 Xpp 类库--如果没有导入对应版本的 Xpp 类库是会报错的)
**MarshallingStrategy **是编组和解组策略的核心接口。(其中,编组过程可以简单的理解为将 JavaBean 对象对应的属性参数逐个读取并按照指定的数据格式进行组合,最后整合成我们需要的 XML 或 JSON 数据格式;依此类推,解组过程就可以理解成是将 xml 或 JSON 数据按照节点的方式进行 JavaBean 类对象属性的读取解析过程)
Mapper 映射器,XStream 通过 XML 数据的 elementName 通过 mapper 获取对应类、成员、属性的 class 对象(这个步骤其实是和 MarshallingStrategy 的解组过程是相辅相成的)。它是支持解组和编组,所以方法是成对存在 real 和 serialized,他的子类 MapperWrapper 作为装饰者,包装了不同类型映射的映射器,如 AnnotationMapper,ImplicitCollectionMapper,ClassAliasingMapper。(这个步骤可以理解为让 mapper 具有了解组和编组各种类的 class 对象的能力---类似做的数学题类型的多少,通过不断累积题目类型才可以解出更多的题目,获取更高的分数)
ConverterLookup 通过 Mapper 获取的 class 对象后,接着调用 lookupConverterForType 获取对应 Class 的转换器,将其转化成对应实例对象。DefaultConverterLookup 是该接口的实现类,同时实现了 ConverterRegistry 的接口,所有 DefaultConverterLookup 具备查找 converter 功能和注册 converter 功能。所有注册的转换器按一定优先级组成由 TreeSet 保存的有序集合(PS:XStream 默认使用了 DefaultConverterLookup)。
2021最新整理网络安全/渗透测试/安全学习/100份src技术文档(全套视频、大厂面经、精品手册、必备工具包、路线)一>获取<一
二、序列化及反序列化调用链分析
写一个简单的测试案例,并在创建 XStream 对象的位置下一个断点,然后开始 debug,看看创建对象过程中 XStream 框架的调用链究竟是什么样的呢?
1、XStream 对象初始化过程利用链及源码分析
StepInto,很明显我们我们进入了 XStream 的无参构造方法中,在这个方法中,传递了默认的接口反射提供者(与其他框架的反序列化方式不同,XStream 利用的是 java 的反射机制--也是为什么 XStream 不用限制 javaBean 类中 setters、getters 方法不用必须实现的原因;也是 JavaBean 类不用实现 Serializeable 接口,重写 readObject()方法的确依然可以进行反序列化的原因)、Mapper 映射器、以及解析器对象的创建(在默认的构造方法中,不难发现依赖的是自包含的 XppDriver 分层流驱动程序,也就是单纯的使用 XMLPullParser()方法进行解析,并未依赖 Xpp3 类库的解析方法)
执行完上面的无参构造方法后,执行参数带有接口反射提供者、Mapper、解析器对象的构造方法中,这次创建了一个扩展类加载器对象(编组或解组过程中,用来尝试加载特性的类)
1 :公共类加载器引用,对上一步构造方法中创建的类加载器对象的引用
2 :创建转换器对象,用于将 Mapper 获取的 class 转换成对应的实例对象
lookupConverterForType 获取对应 Class 的转换器,将其转化成对应实例对象
实现了 ConverterRegistry 的接口,所有 DefaultConverterLookup 具备查找 converter 功能和注册 converter 功能
调用 buildMapper()方法开始构建 Mapper:XStream 构建映射器,是通过 MapperWrapper 装饰者,将各个不同功能的映射器包装成 Mapper
MapperWrapper 装饰者底层代码的逻辑就是将 Mapper 中的方法按不同功能划分成不同实现类,并通过装饰者进行装载(简单的理解是将各种类型的 class 都映射到 mapper 上去,使之具有获取和转换各种 class 实例的能力)
设置好映射器 Mapper 后就完成了 XStream 对象的初始化过程。。。
2、XML 数据反序列化利用链
Xstream 调用 fromXML
①把 String 转化成 StringReader,HierarchicalStreamDriver 通过 StringReader 创建 HierarchicalStreamReader,最后调用 MarshallingStrategy 的 unmarshal 方法开始解组
②marshallingStrategy 创建出 TreeUnmarshaller 来并启动解析
③开始组码—————>TreeUnmarshaller 的 start 方法
④通过节点名获取 Mapper 中对应的 Class
⑤根据 Class 把它转化成对应的 java 对象—————>TreeUnmarshaller 的 convertAnother 方法
⑥如何查找对应的 Converter———>ConverterLookup 中的 lookupConverterForType 方法
⑦根据找到的 Converter 把 Type 转化成 java 对象————>TreeUnmarshaller 的 convert()
组码的过程,当 Class 对应的 Converter 为 AbstractReflectionConverter 时,根据获取的对象,继续读取子节点,并转化成对象对应的变量;获取 class 变量值的过程是一个循环过程,直到读取到最后一个节点推出循环,最终整个反序列化的过程也就结束了,对 XML 数据的解析过程也结束了。。。
三、漏洞成因
通过对 XStream 框架整体的分析不难发现,是程序在调用 XStream 中的 fromXML()方法对 XML 数据进行反序列化的时候,通过绕过 XStream 的黑名单限制而已输入带有任意命令的 xml 格式数据,让反序列化产生了非预期对象,造成了任意命令执行的安全漏洞出现。
四、漏洞 POC 链挖掘思考
XStream 反序列化漏洞屡见不鲜的原因:其实很大程度上来源于 XStream 的“特性”就是不是 JavaBean 类实现 Serializable 接口并重写 readObject()方法。在 JavaBean 类没有实现的时候,XStream 会调用默认的 readOject()方法;而实现的时候,会调用重写的 readObject 方法。在未实现的时候最终结果会返回一个 ReflectionConverter,并且只是处理我们自定义的未实现 Serializable 接口的 JavaBean 类时使用 ReflectionConverter,这时候该 Converter 的原理是通过反射获取类对象并通过反射为其每个属性进行赋值
那么,也就是说归根结底,XStream 反序列化漏洞的原因就是对重写 readObject()方法调用的时候,黑名单控制不严格问题主要引起漏洞形成的。
那么,我们在分析源码的时候,就可以沿着这种方式再重新找到一条实现重写 ReadObject()方法的 XML 返序列化调用链,再在 XML 数据中写入任意命令即可执行了。。。
五、CVE-2020-26259 漏洞复现
使用 IntelliJIDEA,创建一个 maven 项目,在 pom.xml 文件中,给新建的 XStream 项目中引入了 XStream 依赖
然后,将 CVE-2020-26259 漏洞任意文件删除的 POC 写入到 XML 字符串中,调用 XStream 反序列化函数进行反序列化后,观察现象:
执行代码后,对应的文件成功被删除!!(可以删除任意文件)
CVE-2020-26259_SSRF 现象:
poc:
本地利用 nc 监听 8989 端口,观察现象:
总结
终于,把 XStream 整个框架的分析完了。虽然过程很痛苦,及其乏味,但是俺胡汉三还是坚持过来了!!哈哈哈哈,各位大佬们如果发现文章中有什么表达错误的地方欢迎指教。互相交流,互相学习。
评论