重写的readObject()权限修饰符为什么不能是public
字数 1551 2025-08-22 22:47:30
Java反序列化中readObject()方法的权限修饰符探究
0x01 问题背景
在Java反序列化漏洞研究中,初学者常被告知:自定义序列化过程可以通过重写类的readObject方法实现。然而,一个关键细节引发了深入思考:为什么重写的readObject()方法必须是private修饰的?改为public后为何就不执行了?
0x02 方法重写的误解
首先需要澄清几个关键概念:
-
不是传统意义上的方法重写(Overriding):
- 传统重写是子类覆盖父类的非final方法
Object类中并没有readObject()方法Serializable只是一个标记接口,不包含任何方法
-
常见的错误认知:
- 误以为重写的是
ObjectInputStream的readObject()方法 - 实际上
ObjectInputStream.readObject()是public final的,既不能重写也不是我们自定义类的父类方法
- 误以为重写的是
0x03 序列化机制深入解析
真正的机制隐藏在Java序列化API的实现细节中:
ObjectStreamClass的作用
ObjectStreamClass是Java序列化机制中的核心类,它负责:
- 作为类的序列化描述符
- 包装
Class类,提取序列化相关信息 - 包含
serialVersionUID和需要序列化的字段信息
关键源码分析(基于OpenJDK):
// ObjectStreamClass.java
private Method getPrivateMethod(...) {
// 查找指定类中的私有方法
// ...
}
void initPrivateMethods() {
// 查找readObject和writeObject方法
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class }, Void.TYPE);
// ...
}
序列化/反序列化流程
-
序列化时:
ObjectOutputStream通过ObjectStreamClass查找目标类的private writeObject()方法- 如果存在,则调用自定义的序列化逻辑
-
反序列化时:
ObjectInputStream通过ObjectStreamClass查找目标类的private readObject()方法- 如果存在,则调用自定义的反序列化逻辑
- 不存在则使用默认的
defaultReadFields方法
0x04 为什么必须是private
Java序列化机制设计如此的原因:
-
安全考虑:
- 防止外部代码直接调用这些敏感方法
- 确保序列化/反序列化逻辑仅在序列化框架内部使用
-
设计意图:
- 这些方法是给序列化框架调用的,不是给应用程序直接调用的
- 通过反射机制查找和调用,不依赖于传统的继承/重写机制
-
实现细节:
ObjectStreamClass的getPrivateMethod()明确查找私有方法- 即使定义为
public,序列化框架也不会调用
0x05 自定义序列化的正确方式
正确的自定义序列化方法示例:
public class Person implements Serializable {
private String name;
private int age;
// 必须声明为private
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
// 自定义反序列化逻辑
in.defaultReadObject(); // 调用默认反序列化
// 额外的处理...
}
// 对应的writeObject也必须是private
private void writeObject(ObjectOutputStream out)
throws IOException {
// 自定义序列化逻辑
out.defaultWriteObject(); // 调用默认序列化
// 额外的处理...
}
}
0x06 安全启示
-
反序列化漏洞利用:
- 攻击者常利用
readObject()中的不安全操作 - 即使方法为
private,反序列化时仍会被调用
- 攻击者常利用
-
防御措施:
- 避免在
readObject()中执行危险操作 - 使用
ObjectInputFilter限制反序列化的类 - 考虑使用
transient标记敏感字段
- 避免在
0x07 总结
- Java序列化中的"重写"
readObject()实际上是声明一个特定签名的私有方法,而非传统意义上的方法重写 - 序列化框架通过反射查找并调用这些私有方法
- 方法必须声明为
private,这是Java序列化机制的设计要求 - 理解这一机制对研究Java反序列化漏洞至关重要
0x08 进一步研究建议
- 阅读
ObjectStreamClass和ObjectInputStream源码 - 研究Java序列化协议的实现细节
- 探索安全序列化替代方案(如JSON、Protocol Buffers等)
- 了解Java 9+引入的序列化过滤机制