Java 集合反序列化漏洞如何修复避免远程代码执行风险

文章导读
Java 原生序列化机制若直接接收不可信数据,极易引发远程代码执行(RCE)风险。最稳妥的方案是禁止接收不可信数据的反序列化请求,如果业务必须使用,则通过 JDK 自带的序列化过滤机制限制允许的类,并尽快升级存在已知漏洞的第三方依赖库。
📋 目录
  1. 快速处理思路
  2. 为什么会这样
  3. 分步处理
  4. 漏洞复现与验证实操
  5. 常见坑
  6. 参考来源
A A

Java 原生序列化机制若直接接收不可信数据,极易引发远程代码执行(RCE)风险。最稳妥的方案是禁止接收不可信数据的反序列化请求,如果业务必须使用,则通过 JDK 自带的序列化过滤机制限制允许的类,并尽快升级存在已知漏洞的第三方依赖库。

先说结论:Java 反序列化漏洞的核心在于攻击者构造恶意对象链,修复重点在于限制可反序列化的类范围。

  • 先判断:确认业务是否真的必须使用 Java 原生序列化接收外部数据,能不用则不用。
  • 优先做:启用 JDK 序列化过滤(JEP 290,需 JDK 8u121+ 或 JDK 9+)或自定义 ObjectInputStream 白名单,同时升级 CommonsCollections 等高危组件。
  • 再验证:使用 ysoserial 工具生成 Payload 验证漏洞是否闭合,并监控线上日志是否有异常类加载。

快速处理思路

如果没有条件立即重构代码,可以通过 JVM 参数或代码层面增加过滤规则。

注意:JVM 参数 -Djdk.serialFilter 仅在 JDK 8u121+ 及 JDK 9+ 版本生效,低版本 JDK 8 必须通过代码修改。

-Djdk.serialFilter=java.lang.String;java.util.ArrayList;com.example.BusinessClass;!*

或者在代码中自定义 ObjectInputStream 的 resolveClass 方法(见下文分步处理)。

为什么会这样

Java 反序列化漏洞通常不是因为序列化机制本身坏了,而是因为在反序列化过程中,JVM 会实例化数据流中指定的类。如果攻击者能找到一条从入口点到危险操作(如执行命令)的“小工具链”(Gadget Chain),就能在反序列化时触发恶意代码。常见的集合类漏洞往往出现在 Apache Commons Collections 等库中,因为它们提供了丰富的方法调用链。

分步处理

1. 排查入口:搜索项目中所有使用 ObjectInputStreamXMLDecoder 或相关框架配置的地方,确认是否有外部数据流入。

Java 集合反序列化漏洞如何修复避免远程代码执行风险

2. 实施过滤(JDK 8 代码防御):对于不支持 jdk.serialFilter 的旧版本 JDK,需继承 ObjectInputStream 并重写 resolveClass 方法,建立白名单机制。

public class ValidatingObjectInputStream extends ObjectInputStream {
    private final List<String> allowedClasses;

    public ValidatingObjectInputStream(InputStream in, List<String> allowedClasses) throws IOException {
        super(in);
        this.allowedClasses = allowedClasses;
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (!allowedClasses.contains(desc.getName())) {
            throw new ClassNotFoundException("Deserialization rejected: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
}

3. 升级依赖:检查 Maven 或 Gradle 依赖树,将存在反序列化风险的库升级到安全版本。参考以下常见组件安全版本:

  • Apache Commons Collections:避免使用 3.2.1 及以下版本,推荐升级至 3.2.2+ 或 4.0+。
  • Spring Framework:关注官方安全公告,通常建议升级至最新稳定版。
  • 其他:使用 mvn dependency:tree 排查传递依赖,确保无老旧组件。

4. 替换方案:如果可能,将 Java 原生序列化替换为 JSON 等纯数据格式,避免直接实例化对象。

漏洞复现与验证实操

修复后需验证漏洞是否闭合,推荐使用开源工具 ysoserial 生成 Payload 进行测试。

1. 生成 Payload:下载 ysoserial.jar,使用以下命令生成基于 CommonsCollections5 的 Payload(假设目标命令为 calc.exe):

Java 集合反序列化漏洞如何修复避免远程代码执行风险
java -jar ysoserial.jar CommonsCollections5 "calc.exe" > payload.bin

2. 发送请求:将生成的 payload.bin 作为请求体发送给目标接口(注意设置 Content-Type 为 application/octet-stream):

curl -X POST `--data-binary` @payload.bin -H "Content-Type: application/octet-stream" http://target-ip:port/vulnerable-api

3. 观察结果:若修复生效,服务端应抛出 ClassNotFoundException 或拒绝反序列化异常,且不会执行计算器程序。同时检查应用日志,确认是否有被过滤类名的拒绝记录。

常见坑

1. 白名单过宽:使用通配符允许了整个包,导致攻击者利用包内其他类构造新链。建议精确到类名。

2. 忽略内部类:静态内部类也需要单独加入白名单,否则反序列化时会失败或被绕过。

3. 框架默认配置:某些框架默认开启反序列化支持,需在配置文件中显式关闭或限制。

4. JDK 版本差异:误以为所有 JDK 8 都支持 jdk.serialFilter,导致在低版本更新号上配置无效。

参考来源

  • Oracle OpenJDK, JEP 290: Serialization Filtering, https://openjdk.org/jeps/290
  • OWASP, Deserialization Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html