根本原因是java原生序列化默认信任数据来源,缺乏完整性校验、类型约束与行为管控机制,暴露于网络时易被构造恶意字节流触发任意代码执行,且类路径失控导致白名单等防御易被绕过。

安全专家不建议在生产环境使用原生 Java 序列化,根本原因不是它“不能用”,而是它的设计契约与现代分布式系统的真实运行环境存在不可调和的冲突——它默认信任数据来源,且缺乏内置的完整性、类型和行为约束机制。
Java序列化天生基于“可信环境”假设
从 JDK 1.1 设计之初,java.io.Serializable 就被定位为 JVM 内部通信工具,而非面向网络边界的安全协议。它隐含三个关键前提:
- 字节流只在同版本、同信任域的 JVM 之间流转(比如同一台机器上的两个线程)
- 反序列化前,数据已由可信通道(如本地文件系统、受控内存队列)保障其完整性和来源
- 类路径中所有可能被还原的类都是开发者明确控制、静态可分析的
一旦把序列化字节流暴露给 HTTP 请求体、Cookie、消息队列或前端传参——这个契约就彻底失效。攻击者不需要入侵代码,只需构造一段合法但恶意的字节流,就能触发任意类的 readObject()、静态初始化块,甚至利用第三方库(如 Commons Collections、Groovy、JDK 自带的 AnnotationInvocationHandler)形成利用链。
没有校验能力:字节流可被任意篡改
原生序列化协议不包含签名、哈希或加密机制。一个 Base64 编码的 RememberMe=xxx Cookie(如 Shiro 默认配置),本质就是一串裸露的、可被完全逆向和重写的序列化数据。攻击者无需知道服务端类名,只要目标环境中存在一条可用的 gadget chain,就能通过 ysoserial 等工具一键生成 RCE payload。
更隐蔽的是:这类漏洞往往不报错。恶意对象可能静默执行命令、建立反连、写入临时文件,而日志里只留下一句 “ClassNotFoundException” 或干脆无痕——因为出问题的不是反序列化本身,而是它无意中加载并执行了不该执行的逻辑。
类路径失控导致防御失效
反序列化过程会动态加载类,而 JVM 并不验证该类是否“应该被加载”。即使你用白名单限制了顶层类,只要某个被允许的类内部引用了危险类(比如通过反射、Class.forName、或 TemplatesImpl 的 _bytecodes 字段),整个过滤就形同虚设。
实际生产环境中,依赖树动辄数百个 jar 包,其中可能混入老旧、未更新、或开发时临时引入的组件(如低版本的 Apache Commons BeanUtils、Jackson 某些早期版本)。这些组件自身就携带可被链式调用的反序列化入口点,而开发者通常并不知晓它们的存在。
兼容性与维护成本远超收益
除了安全风险,原生序列化还带来长期运维负担:
-
serialVersionUID 管理极易出错:字段增删、类型变更、IDE 自动生成差异,都会导致
InvalidClassException,线上反序列化失败常表现为“数据丢失”而非明确报错 -
内部类/lambda 不支持安全反序列化:编译器生成的合成字段(如
this$0)在不同 JDK 或编译器下行为不一致,造成灰度发布时部分节点反序列化失败 - 无法跨语言:字节流格式是 JVM 私有协议,Go、Python、Node.js 服务无法直接解析,违背微服务多语言协作趋势
相比之下,JSON、Protobuf、Avro 等替代方案天然具备结构描述、Schema 约束、语言中立和可读性等特性,既能满足传输与持久化需求,又从根本上规避了“执行任意代码”的风险模型。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











