RSS Feed
更好更安全的互联网

Java 反序列化之 CommonsBeanUtils 分析

2016-03-08

Author: rungobier (知道创宇404安全实验室)

Date: 2016-03-04

一、简介

前几天看到 github 上的 ysoserial 更新至0.0.4,增加了 CommonsBeanUtils Java反序列化 Payload 生成代码,原以为跟前面的 CommonsCollections 的原理一样,仔细看了一遍思路大不相同。CommonsBeanutilsCollectionsLogging1 主要依赖的 jar 包有:commons-collections(2.0-3.2.2), commons-beanutils-1.9.2, commons-loggings-1.2

二、序列化

CommonsBeanutilsCollectionsLogging1 的主要代码如下:

CommonsCollections payload 生成过程当中,需要形成反序列化的调用链。刚开始我以为这个是 CommonsCollections 的更新升级版,特意追了下 commons-collections 的相关代码,发现 commons-collections 在这里只是起到辅助作用,仅在 BeanComparator 中用到了 ComparableComparator 这个类,追踪了下 ComparableComparator 的源代码,它从 commons-collections-2.0 就已经存在了,而且在最新版本的 commons-collections 也未做较大改动。

下面是 BeanComparator 的相关代码:

回归正题,在今天这个 payload 生成过程当中也需要形成反序列化的调用链,PriorityQueue 是符合这个条件的,其自身实现了 readObjectPriorityQueue 是使用数组实现的完全二叉树优先队列,不允许空值,而且不支持 non-comparable 的对象。在最终达到 Runtime.exec 之前,需要解决以下几个问题:

  • 放入PriorityQueue的对象需要实现readObject
  • 要实现Comparable接口

经过第一个条件的过滤之后,可以发现 jdk 中的 TemplatesImpl 类可以满足条件。

但是,TemplatesImpl 未实现 Comparable 接口,为了绕过这个,可以看到代码当中首先添加了两个 BigInteger 值为1的对象。BeanComparator 中设置的比较属性为 lowestSetBit,这里也可以改为 BigInteger 其它的可比较属性名称,前期分析过程当中原以为是为了在比较过程当中利用特定的属性触发某些条件,继续跟踪下去发现根本不是。

由于PriorityQuque的泛型类型设置为了Object,所以是任何实现了Comparable接口的对象都可以放进去的。那么不可比较的templates对象怎么处理?

首先,利用 Java 反射机制将 Comparatorproperty 设置为 TemplatesImpl 的属性 outputProperties,但是这个属性在这里未起到比较的作用。它的重要作用将在 payload 反序列化时体现。

其次,我们看到代码利用反射机制直接获取了 PriorityQueue 的内置属性数组 queue,将 templates 按照索引值填入了 queue,这样做是利用了 Java 的泛型的类型擦除,这里简单介绍下类型擦除,早期的 Java 语言是不支持泛型的,后来在Java5 当中加入了泛型支持,在 Java 编译阶段将具体的类型信息擦除了,所以在 Java 的泛型代码内部,是无法获得任何有关泛型参数类型的信息。这样就规避掉了条件二的限制,并且在序列化数据中保留了 templates 的类型信息,不得不说这段代码实现的非常精巧。

如果不做上述的处理,直接使用 queue.add 方法添加 templates,在生成 payload 时,将会触发 Java SecurityManager 安全机制,抛出异常。

最后,这段代码中精心构造的 PriorityQueue 对象,包含的两个 TemplatesImpl 对象被序列化,相关被序列化的还有 BeanComparator 对象的属性 property ,它的值为 outputProperties

下面我们看看对象 templates 的生成相关代码:

上述代码中的重点是利用了 Javaassist 这个动态代理库,这个库在我看来实现了 Java 当中的元编程,就是让 Java 代码在运行当中动态编写可以运行的代码。利用它为 TemplatesImpl 对象的属性 _bytecodes 填入了静态内置类 StubTransletPayload 的字节码,动态代理库在这个静态内置类的静态初始化方法中加入了需要执行的指令,一般都为Runtime.exec

三、反序列化

上面简单分解了序列化 payload 生成过程,这里将剖析反序列化的过程。

首先,我们看看 PriorityQueuereadObject() 函数调用过程:

从上面的代码可以看出 PriorityQueue 在反序列化过程中对队列当中的元素做了比较排序,调用了 Comparator 进行元素比较。进一步跟进 BeanComparator compare 方法:

使用了 PropertyUtils 类的 getPropety 方法,代码在这里就不贴了,其实就是调用了对象(templates) Bean 方法(getOutputProperties),而 TemplatesImpl 类的这个方法的具体内容就是关键的一句话 return newTransformer().getOutputProperties();  newTransformer 方法的后续关键代码如下:

从代码中可以看到,newTransformer 中调用了 TransformerImpl 的构造函数,此构造函数的第一个参数就调用了一个私有的 getTransletInstance() 函数,前面生成templates对象时,没有给它的成员变量 _class 赋值,所以接着调用了 defineTransletClasses() 函数,最后在 defineTransletClasses() 函数中可以看到定义了一个类加载器(TransletClassLoader),使用这个类加载器加载 _bytecodes 成员变量的字节码,通过前面的梳理可以知道是 Gadgets 类的内部静态类 StubTransletPayload,当这个类加载成功后,有 Javaassist 动态注入的静态初始化方法就会执行,也就是我们最终的目标:  Runtime.exec

四、验证

为了验证上述的推理过程,我们可以用如下的两段代码进行调试验证:

五、调用链

最终分析出的反序列化调用链如下:

 

六、影响范围

去年受到 Java 反序列化影响的容器、应用软件若是依靠升级 commons-collections 来处理漏洞,同时在 Java 的运行环境当中包含了 commons-beanutils.jar commons-logging.jar,是仍然有可能受到 Java 反序列化的攻击的。

目前并没有相关官方补丁来修复该问题,临时解决方案是检查应用业务对外接口,尽量禁止对外的序列化数据接口。

作者:niubl | Categories:安全研究 | Tags:

发表评论