Java web学习之路-序列化和反序列化
字数 1128 2025-08-18 11:38:22
Java序列化与反序列化深度解析
序言
Java序列化与反序列化是Java安全中的重要概念,也是许多安全漏洞的根源。本文将全面解析Java序列化机制,包括基础概念、实现方式、安全风险以及实际利用方法。
一、序列化基础
1.1 序列化概念
Java序列化是指把Java对象转换为字节序列的过程,便于保存在内存、文件或数据库中。反序列化则是把字节序列恢复为Java对象的过程。
关键类与方法:
ObjectOutputStream.writeObject()- 序列化方法ObjectInputStream.readObject()- 反序列化方法
1.2 序列化条件
一个类要可序列化必须满足:
- 实现
java.io.Serializable接口 - 所有属性必须是可序列化的
1.3 基本示例
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public String identify;
public void mailCheck() {
System.out.println("This is the "+this.identify+" of our company");
}
}
序列化操作:
FileOutputStream fileOut = new FileOutputStream("employee1.obj");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
二、序列化实现方式
2.1 Serializable接口
最常用的序列化方式,只需实现Serializable接口即可。
特性:
- 只保存实例变量,不保存静态变量
- 父类实现Serializable,子类自动可序列化
- 会保存private变量,存在安全隐患
- 不会序列化
transient修饰的变量 - 反序列化时不调用构造方法
2.2 transient关键字
阻止变量被序列化:
private transient String password;
2.3 Externalizable接口
继承自Serializable,增加了两个方法:
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
特点:
- 完全控制序列化过程
transient关键字无效- 必须提供无参构造函数
示例:
class Person implements Externalizable {
transient String userName; // 即使使用transient也会被序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(userName);
// 自定义序列化逻辑
}
@Override
public void readExternal(ObjectInput in) throws IOException {
userName = (String) in.readObject();
// 自定义反序列化逻辑
}
}
三、反序列化机制
3.1 基本反序列化
FileInputStream fileIn = new FileInputStream("employee1.obj");
ObjectInputStream in = new ObjectInputStream(fileIn);
Employee e = (Employee) in.readObject();
in.close();
fileIn.close();
3.2 readObject方法
反序列化的核心是readObject()方法,可以被重写以实现自定义行为:
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 默认反序列化
// 自定义逻辑
}
四、安全风险与利用
4.1 反序列化漏洞原理
通过重写readObject()方法,可以在反序列化时执行任意代码:
class ObjectCalc implements Serializable {
public String name;
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec("open /Applications/Calculator.app/");
}
}
4.2 漏洞利用步骤
- 创建恶意对象并序列化:
ObjectCalc myObj = new ObjectCalc();
FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(myObj);
os.close();
- 受害者反序列化时触发:
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
ObjectCalc objectFromDisk = (ObjectCalc)ois.readObject();
ois.close();
五、防御措施
- 避免反序列化不可信数据
- 使用
ObjectInputFilter验证反序列化类 - 对敏感字段使用
transient - 重写
readObject()添加验证逻辑 - 考虑使用JSON等替代序列化方式
六、关键总结
- Java序列化特征:
aced0005开头 - 序列化保存实例变量,不保存静态变量
transient阻止序列化,但对Externalizable无效- 反序列化不调用构造方法
readObject()重写是漏洞根源- 反序列化顺序必须与序列化顺序一致
参考资源
完整示例代码可在GitHub获取:Java序列化示例代码