简介:Java反序列化漏洞利用门槛低、破坏力强,曾在大量开源组件与应用中间件中引发严重安全事件。本文从序列化机制入手,剖析漏洞成因与利用链原理,给出代码审计、依赖扫描等检测方法及白名单过滤等系统性防御方案,帮助开发者有效识别并收敛此类风险。
Java反序列化漏洞是近年安全业界重点研究的领域之一。安全研究人员在众多常见的开源组件库、应用中间件以及企业级应用服务器中均发现过此类漏洞。这类漏洞具有三个显著特点:
本文将先剖析漏洞产生的底层原理,再介绍安全检测与扫描的思路,最后给出系统性的防御加固方案。
序列化是把Java对象转换为字节序列的过程,转换后的字节数据可以保存到文件、数据库中,也可以通过网络传输;反序列化则是把字节序列恢复为Java对象的逆过程。整个过程可以概括为:
Java对象 —序列化—> 字节序列 —存储/传输—> 字节序列 —反序列化—> Java对象
序列化和反序列化通过ObjectOutputStream.writeObject()与ObjectInputStream.readObject()两个方法实现。在Java中,任何类如果想要被序列化,必须实现java.io.Serializable接口:
public class Hello implements java.io.Serializable {String name;}
值得注意的是,java.io.Serializable是一个空接口,没有任何需要实现的方法。它的唯一作用是给类打上“可序列化”的标记,让JRE在运行时确认这个类可以被序列化。
Java还支持在类中定义以下两个方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOExceptionprivate void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException
这两个函数并非Serializable接口的接口函数,而是开发者与序列化机制之间的“约定”:如果一个类定义了它们,那么在序列化和反序列化的时候,writeObject()与readObject()会主动回调这些方法,给予开发者自定义处理逻辑的机会。
这一“数据驱动、自动回调”的机制,正是反序列化漏洞产生的根本原因。
public class Hello implements java.io.Serializable {String name;private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException {in.defaultReadObject();Runtime.getRuntime().exec(name); // 危险:反序列化时执行命令}}
当应用对该类对象执行反序列化时,readObject回调会被自动触发,name字段中存储的命令将被执行。攻击者只需构造一个序列化的Hello对象并把name设置为恶意命令,应用在反序列化这份数据时就会执行该命令。
理解这类漏洞的关键在于“数据与代码的边界”:
这条由多个类与方法调用串联而成的路径,被称为“利用链”。因此,反序列化攻击必须借助被攻击应用的具体场景来实现:目标类路径中须存在可用的利用链,且反序列化入口接收了攻击者可控的数据。
主流Java应用通常依赖大量开源组件,组件之间又相互依赖,导致运行时类路径非常庞大。只要其中某个(或某组)类存在可被串联的敏感行为,就可能被组合成利用链。历史上多起影响深远的事件,都源于被广泛复用的通用组件存在此类问题,再被各类中间件与应用服务器继承放大,最终形成大范围的影响。
排查时需要重点关注以下可能接收序列化数据的场景:
大多数反序列化漏洞来自第三方依赖。建议在构建流程中集成依赖漏洞扫描能力(如软件成分分析类工具),持续比对组件版本与已知漏洞库,及时升级存在风险的版本。使用云环境的团队,也可借助云平台提供的漏洞扫描、镜像检测等通用安全服务,对主机、容器镜像与应用依赖做统一治理。
这是最根本的原则。凡是来源不可控的数据(网络输入、用户上传文件等),都不应执行Java原生反序列化。跨系统传输对象时优先采用JSON等纯数据格式,或使用显式字段绑定的序列化方案,避免携带并自动恢复任意类型信息。
较新版本的JDK提供了序列化过滤机制,可通过ObjectInputFilter对允许反序列化的类进行限制:
ObjectInputStream ois = new ObjectInputStream(inputStream);ois.setObjectInputFilter(filterInfo ->filterInfo.serialClass() != null&& WHITELIST.contains(filterInfo.serialClass().getName())? ObjectInputFilter.Status.ALLOWED: ObjectInputFilter.Status.REJECTED);
对旧版本运行环境,可在反序列化入口重写resolveClass方法,在类加载前校验类名是否在白名单内,拒绝加载任何非预期类。
Java反序列化漏洞的本质,是“序列化数据携带类型信息 + 反序列化自动回调方法”这一设计在不可信输入场景下安全边界失守。攻击者无法直接控制代码,但可以通过精心构造的数据,驱动应用类路径中已有的类与方法调用链完成攻击。
防御思路也应由此展开:从源头避免反序列化不可信数据,用白名单严格约束可恢复的类型,持续治理依赖、修复已知利用链,再辅以运行时监控与纵深防御。多层措施结合,才能有效收敛这一类利用门槛低、影响广泛、破坏力强的安全风险。