Java序列化和反序列化基础

一、序列化与反序列化

static静态属性和被关键字(transient)声明的不会参加到序列化和反序列化的操作中的

1.1 什么是序列化 (ac ed开头)

序列化是指把 Java 对象转换为字节序列(二进制)的过程,目的是便于保存在内存、文件、数据库中。

ObjectOutputStream 类的 writeObject() 方法可以实现序列化。

writeObject()方法:将指定的对象写入 ObjectOutputStream 中。

一个类的对象要想序列化成功,必须满足两个条件:

  1. 该类必须实现 java.io.Serializable 接口
  2. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SerializeDemo {
public static void main(String [] args) throws IOException {

HackInfo hack = new HackInfo();
hack.id = "Pasdasd";
hack.team = "demodemo";

//将序列化后的字节序列写到serializedata.txt文件中
FileOutputStream fileOut = new FileOutputStream("D:\\abc\\demo1.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
System.out.println(out);
out.writeObject(hack);
out.close();
fileOut.close();
System.out.println("序列化的数据已经保存在了demo1.txt文件中");
}
}

1.2 什么是反序列化

反序列化是指把字节序列恢复为 Java 对象的过程。

ObjectInputStream 类的 readObject() 方法可实现反序列化。

readObject()方法:从ObjectInputStream读取一个对象

1、当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个 Java 进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个 Java 对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。

2、所以 Java 序列化一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

① 、想把内存中的对象保存到一个文件中或者数据库中时候;

② 、想用套接字在网络上传送对象的时候;

③ 、想通过RMI传输对象的时候

一些应用场景,涉及到将对象转化成二进制,序列化保证了能够成功读取到保存的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DeserializeDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {

FileInputStream fileIn = new FileInputStream("D:\\abc\\demo1.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
HackInfo hack = (HackInfo) in.readObject();
//Object hack = in.readObject();
in.close();
fileIn.close();

System.out.println("反序列化恢复字节序列为对象...");
System.out.println("Name: " + hack.id);
System.out.println("Address: " + hack.team);
//System.out.println("Address: " + hack);
}
}

二. 不进行序列化和反序列化的操作

static静态属性和被关键字(transient)声明的不会参加到序列化和反序列化的操作中的

序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package demo2;
import java.io.*;
public class SerializeDemo2 {
public static void main(String [] args) throws IOException {

HackInfo2 hack2 = new HackInfo2();
hack2.id = "demo222";
hack2.team = "demo";
hack2.password = "6666";
hack2.address = "beijing";

//将序列化后的字节序列写到serializedata.txt文件中
FileOutputStream fileOut = new FileOutputStream("D:\\abc\\demo1.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
System.out.println(out);
out.writeObject(hack2); //序列化操作
out.close();
fileOut.close();
System.out.println("序列化的数据已经保存在了serializedata.txt文件中");

}
}

创建一个类:

1
2
3
4
5
6
7
8
9
10
11
package demo2;
import java.io.Serializable;
public class HackInfo2 implements Serializable {

private static final long serialVersionUID = -1519648909563808141L;
public String address;
public String team;
public transient String password; // 不需要序列化的字段

public static String id; //static 不进行(反)序列化的操作
}

反序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package demo2;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {

FileInputStream fileIn = new FileInputStream("D:\\abc\\demo1.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
HackInfo2 hack2 = (HackInfo2) in.readObject();
in.close();
fileIn.close();

System.out.println("反序列化恢复字节序列为对象...");
System.out.println("Name: " + hack2.id);
System.out.println("team: " + hack2.team);
System.out.println("password: " + hack2.password);
System.out.println("address: " + hack2.address);
}
}


反序列化恢复字节序列为对象...
Name: null
team: demo
password: null
address: beijing

三. 反序列化漏洞:

在 Java 中有这么一种情况,我们都知道如果一个类实现了 Serializable 接口,那么它的对象可以被序列化和反序列化。

但如果类中定义了 private void readObject(ObjectInputStream ois) 方法,在反序列化过程中,这个方法会在默认的反序列化机制执行之前被调用,允许你在对象反序列化时执行一些自定义的逻辑。

如果你在你的类中重写了 readObject 方法,那么在反序列化这个类的对象时,JVM 会调用你重写的readObject 方法而不是默认的反序列化机制。这样可以让你在反序列化过程中自定义对象的初始化或者执行其他逻辑。

但如果你想要执行完重写的 readObject 反序列化动作后,还想继续使用原反序列化操作,那就需要使用到 defaultReadObject 方法,不然执行完自定义的动作后就停止了。也就是说我们重写的 readObject 执行顺序在默认反序列化操作之前。

既然,我们可以在要序列化的类中重写 readObject,这也就意味着给了我们一个攻击的入口,可以实现在服务器上运行代码的,执行命令等能力。

反序列化漏洞两个基础条件:

  1. 实现了 java.io.Serializable 接口
  2. 重写了 readObject 方法

步骤:

  1. 写一个类(重写了 readObject 方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package demo3;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;


public class HackInfo3 implements Serializable {
private static final long serialVersionUID = -8619751142754444841L;
public String id;
public String team;

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();

Runtime.getRuntime().exec("calc.exe");

}
}
  1. 序列化代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package demo3;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;

    public class SerializeDemo3 {
    public static void main(String [] args) throws IOException {
    HackInfo3 hack3 = new HackInfo3();
    hack3.id = "demo1";
    hack3.team = "demo";

    //将序列化后的字节序列写到serializedata.txt文件中
    FileOutputStream fileOut = new FileOutputStream("D:\\abc\\demo1.txt");
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    System.out.println(out);
    out.writeObject(hack3);
    out.close();
    fileOut.close();
    System.out.println("序列化的数据已经保存在了serializedata.txt文件中");
    }
    }

    反序列化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package demo3;

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;

    public class DeserializeDemo3 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

    FileInputStream fileIn = new FileInputStream("D:\\abc\\demo1.txt");
    ObjectInputStream in = new ObjectInputStream(fileIn);
    //HackInfo3 hack3 = (HackInfo3) in.readObject();
    Object hack3 = in.readObject();
    System.out.println("Object:" + hack3.getClass());
    in.close();
    fileIn.close();

    System.out.println("反序列化恢复字节序列为对象...");
    /* System.out.println("Name: " + hack3.id);
    System.out.println("Address: " + hack3.team);*/

    }
    }

    在反序列化的时候,JVM 会调用你重写的readObject 方法(里面有弹出计算器的恶意程序会执行)而不是默认的反序列化机制。


Java序列化和反序列化基础
http://example.com/2025/06/05/Java序列化和反序列化基础/
作者
XCDH
发布于
2025年6月5日
许可协议