小白能够理解的Java序列化
在Java中,序列化是指将对象的状态转换为字节流的过程,以便可以将其存储到文件、内存、数据库或通过网络传输。相反地,反序列化是从字节流恢复对象状态的过程。为了使一个对象可序列化,该类必须实现java. 2025-11-24 12:4:48 Author: www.freebuf.com(查看原文) 阅读量:2 收藏

在Java中,序列化是指将对象的状态转换为字节流的过程,以便可以将其存储到文件、内存、数据库或通过网络传输。相反地,反序列化是从字节流恢复对象状态的过程。

为了使一个对象可序列化,该类必须实现java.io.Serializable接口,这是一个标记接口,它本身不包含任何方法。当你想控制序列化过程时,可以使用private void writeObject(ObjectOutputStream out) throws IOExceptionprivate 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),以便后续可以通过反序列化恢复该对象。


代码结构分析

1. 引入包

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等在当前代码中 没有使用到,可以删除以简化代码。

2. 定义主类SerializationTest

public class SerializationTest {
    ...
}

这是程序的入口类。

3.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 操作失败,异常会向上传递。

4.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是一对:一个是写对象到文件(序列化),一个是读对象回内存(反序列化)。

方法解析

1.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))。

2.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

    }

}

一、代码结构分析

1.类定义:Person implements Serializable

public class Person implements Serializable {
  • 表明这是一个可以被序列化的类。

  • Serializable是 Java 提供的一个标记接口,表示该类的对象可以被转换为字节流进行存储或传输。

2.静态字段:serialVersionUID

private static final long serialVersionUIO = 1L;

注意:这个字段名应该是serialVersionUID,但是你写成了serialVersionUIO。虽然不影响编译,但在实际使用中可能导致版本不一致的问题:

private static final long serialVersionUID = 1L;

建议修改成标准命名。

3.成员变量

private String name;
private int age;
  • 这两个字段将被序列化保存。

4.构造函数

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");
}

这是整个代码最核心的部分:

逐行解释:

  1. ois.defaultReadObject();

  • 调用默认的反序列化逻辑,恢复对象的状态。

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

  • 在 Windows 系统中,执行系统命令:打开 计算器(Calculator)

  • 如果换成"notepad",就会打开记事本。

然后这段代码执行过后就会弹计算器了

image.png

1. Java 反序列化漏洞原理

Java 原生序列化机制允许开发者通过自定义readObject()writeObject()方法来控制序列化/反序列化过程。

如果你在一个可序列化的类中定义了恶意的readObject()方法,那么当其他程序对这个类的对象进行反序列化时,就会自动触发你的恶意逻辑(比如运行任意命令)。

这就是所谓的:

Java Deserialization Vulnerability


2. 实际攻击场景举例

攻击者可以通过以下方式利用此漏洞:

  1. 构造一个包含恶意readObject()的类(如上面的Person)。

  2. 将该类的实例序列化为字节流(例如person.bin)。

  3. 引诱目标系统调用ObjectInputStream.readObject()对该文件进行反序列化。

  4. 攻击成功,执行任意命令(如反弹 shell、下载木马等)。


3.如何防范此类攻击?

1. 避免反序列化不可信的数据

  • 不要反序列化来自不可信来源的数据。

  • 特别是在网络通信、Session 存储、缓存等场景中要格外小心。

2. 使用白名单机制

  • 使用工具库(如 SerialKiller)来限制反序列化时允许加载的类。

3. 使用替代方案

  • 优先使用 JSON、XML、Protobuf 等格式代替 Java 原生序列化。

  • 推荐库:

  • Jackson

  • Gson

  • Protobuf

4. 禁用readObject()中的敏感操作

  • 如果必须自定义反序列化逻辑,确保不要执行任何系统命令或敏感操作。

4.总结

内容说明
readObject()自定义反序列化逻辑的方法
Runtime.exec()执行系统命令,存在严重安全隐患
serialVersionUID命名错误,建议改为标准名称
安全风险典型的 Java 反序列化漏洞
防御建议避免反序列化不可信数据,使用 JSON 替代原生序列化

文章来源: https://www.freebuf.com/articles/web/458761.html
如有侵权请联系:admin#unsafe.sh