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 | 攻击者构造的序列化数据 |
经典入口:HashMap 的 readObject()
为什么 HashMap 是最常用的入口?
HashMap 实现了 Serializable
HashMap.readObject() 会重建哈希表,对每个 key 调用 key.hashCode()
攻击者可以控制 HashMap 中的 key 是什么对象
HashMap.readObject() 简化逻辑
1 | // java.util.HashMap 源码简化 |
如果 key 是一个精心构造的恶意对象,它的 hashCode() 方法可能会进一步触发危险操作
完整触发链
1 | HashMap.readObject() |
经典 Sink:几种代码执行方式
1. Runtime.exec() — 直接执行命令
1 | Runtime.getRuntime().exec("calc.exe"); |
最直接,但很多 gadget chain 到不了这里
2. TemplatesImpl — 加载恶意字节码
1 | // com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl |
原理:TemplatesImpl 内部会用 ClassLoader 加载 _bytecodes 中的字节码
恶意类的 static {} 块或构造函数中放入 Runtime.exec()
CommonsBeanutils 链和 ROME 链都走这条路
3. JNDI 注入 — 远程加载恶意类
1 | InitialContext ctx = new InitialContext(); |
攻击者控制 JNDI URL → 指向自己的 LDAP/RMI 服务 → 返回恶意类
Spring AOP 链和 XBean 链走这条路
4. 反射调用
1 | Method method = Runtime.class.getMethod("exec", String.class); |
CommonsCollections 系列链通过 InvokerTransformer 实现反射调用
ysoserial:Gadget Chain 自动生成工具
简介
ysoserial 是最著名的 Java 反序列化利用工具
它封装了多条 Gadget Chain,可以一键生成恶意序列化数据
只适用于 Java 原生反序列化(ObjectInputStream),不适用于 Hessian2
使用方法
1 | 列出所有可用的 gadget chain |
常用链速查表
| 链名 | 依赖库 | 入口 | 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详解 |