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. 排查入口:搜索项目中所有使用 ObjectInputStream、XMLDecoder 或相关框架配置的地方,确认是否有外部数据流入。
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;}@Overrideprotected 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 -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
原文链接:https://www.zjcp.cc/ask/11772.html
