java代码审计之反序列化基础1
字数 1842 2025-08-19 12:42:22

Java反序列化基础教学文档

一、Java反序列化概念

1. 序列化与反序列化定义

  • 序列化:将对象转换为字节序列的过程
    • 目的:将数据转化成字节流,以便存储在文件中或在网络上传输
  • 反序列化:把字节序列恢复为对象的过程
    • 目的:打开字节流并重构对象

2. 为什么需要序列化/反序列化

当两个Java进程进行通信时,需要实现进程间的对象传输,这时就需要:

  1. 发送方将对象序列化为字节流
  2. 通过网络传输字节流
  3. 接收方将字节流反序列化为对象

二、序列化实现基础

1. 可序列化条件

  • 类必须实现java.io.Serializable接口
    • Serializable是一个标记接口(空接口,无抽象方法)
    • 未实现该接口的类尝试序列化会抛出java.io.NotSerializableException

2. 阻止序列化的方法

  • 使用transient关键字修饰属性
    • transient修饰的属性不会被序列化
    • 反序列化时,transient字段会被设为默认值(如String为null,int为0)

三、代码实现详解

1. 基础类定义(Person.java)

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

public class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(){}
    
    // 构造函数
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString(){
        return "Person{" + "name='" + name + "', age=" + age + '}';
    }
}

2. 序列化示例(SerializationTest.java)

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        System.out.println(oos);
    }
    
    public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        serialize(person);
    }
}

关键代码解析:

  • ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    • 创建ObjectOutputStream对象,用于将对象序列化成字节流
    • 写入名为"ser.bin"的文件(不存在则创建)
  • oos.writeObject(obj);
    • 将传入的对象写入输出流,实现序列化

3. 反序列化示例(UnserializeTest.java)

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("ser.bin");
        System.out.println(person);
    }
}

关键代码解析:

  • ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    • 创建ObjectInputStream对象,用于从文件读取字节流并反序列化
  • Object obj = ois.readObject();
    • 从输入流中读取对象字节流并反序列化为Java对象

四、安全风险与漏洞原理

1. 漏洞根源

  • Java反序列化漏洞的根本原因与readObject方法有关
  • 在反序列化过程中,readObject方法中的代码会自动执行

2. 危险示例

private void readObject(ObjectInputStream ois) 
    throws IOException, ClassNotFoundException {
    ois.defaultReadObject();  // 默认反序列化逻辑
    Runtime.getRuntime().exec("calc");  // 恶意代码
}
  • 当反序列化包含上述readObject方法的对象时:
    1. 先执行默认反序列化逻辑(defaultReadObject
    2. 然后执行Runtime.getRuntime().exec("calc"),启动计算器

3. 漏洞利用条件

  1. 攻击者能够控制反序列化的数据源
  2. 目标应用中存在可被利用的类(包含危险方法的类)
  3. 这些类在应用的classpath中

五、防御措施

  1. 输入验证:不要反序列化不受信任的数据
  2. 使用白名单:限制可反序列化的类
  3. 使用替代方案
    • 使用JSON、XML等更安全的序列化格式
    • 使用第三方安全库如SerialKiller
  4. 更新依赖:及时修复已知漏洞的库(如Apache Commons Collections)
  5. 安全管理器:配置适当的安全策略

六、审计要点

  1. 查找反序列化入口点

    • ObjectInputStream.readObject()调用
    • 网络通信、文件读取等可能接收外部输入的地方
  2. 检查可序列化类

    • 检查readObjectreadResolve等方法实现
    • 检查是否有危险操作(如命令执行、文件操作等)
  3. 检查依赖库

    • 检查是否使用了已知存在漏洞的库版本
    • 如Apache Commons Collections、XStream等

七、实验验证

  1. 验证Serializable接口必要性

    • 移除implements Serializable观察序列化失败
  2. 验证transient关键字效果

    • 添加transient修饰符观察字段不被序列化
  3. 验证恶意readObject方法

    • 添加命令执行代码观察反序列化时执行

八、总结

Java反序列化漏洞是Java安全中的重要议题,其核心在于:

  • 反序列化过程会自动执行readObject方法
  • 攻击者可通过构造恶意序列化数据触发危险操作
  • 防御需要从输入控制、类限制、安全编码等多方面入手

理解这些基础知识是进行Java代码审计和漏洞挖掘的重要前提。

Java反序列化基础教学文档 一、Java反序列化概念 1. 序列化与反序列化定义 序列化 :将对象转换为字节序列的过程 目的:将数据转化成字节流,以便存储在文件中或在网络上传输 反序列化 :把字节序列恢复为对象的过程 目的:打开字节流并重构对象 2. 为什么需要序列化/反序列化 当两个Java进程进行通信时,需要实现进程间的对象传输,这时就需要: 发送方将对象序列化为字节流 通过网络传输字节流 接收方将字节流反序列化为对象 二、序列化实现基础 1. 可序列化条件 类必须实现 java.io.Serializable 接口 Serializable 是一个标记接口(空接口,无抽象方法) 未实现该接口的类尝试序列化会抛出 java.io.NotSerializableException 2. 阻止序列化的方法 使用 transient 关键字修饰属性 被 transient 修饰的属性不会被序列化 反序列化时, transient 字段会被设为默认值(如String为null,int为0) 三、代码实现详解 1. 基础类定义(Person.java) 2. 序列化示例(SerializationTest.java) 关键代码解析: ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); 创建 ObjectOutputStream 对象,用于将对象序列化成字节流 写入名为"ser.bin"的文件(不存在则创建) oos.writeObject(obj); 将传入的对象写入输出流,实现序列化 3. 反序列化示例(UnserializeTest.java) 关键代码解析: ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); 创建 ObjectInputStream 对象,用于从文件读取字节流并反序列化 Object obj = ois.readObject(); 从输入流中读取对象字节流并反序列化为Java对象 四、安全风险与漏洞原理 1. 漏洞根源 Java反序列化漏洞的根本原因与 readObject 方法有关 在反序列化过程中, readObject 方法中的代码会自动执行 2. 危险示例 当反序列化包含上述 readObject 方法的对象时: 先执行默认反序列化逻辑( defaultReadObject ) 然后执行 Runtime.getRuntime().exec("calc") ,启动计算器 3. 漏洞利用条件 攻击者能够控制反序列化的数据源 目标应用中存在可被利用的类(包含危险方法的类) 这些类在应用的classpath中 五、防御措施 输入验证 :不要反序列化不受信任的数据 使用白名单 :限制可反序列化的类 使用替代方案 : 使用JSON、XML等更安全的序列化格式 使用第三方安全库如SerialKiller 更新依赖 :及时修复已知漏洞的库(如Apache Commons Collections) 安全管理器 :配置适当的安全策略 六、审计要点 查找反序列化入口点 : ObjectInputStream.readObject() 调用 网络通信、文件读取等可能接收外部输入的地方 检查可序列化类 : 检查 readObject 、 readResolve 等方法实现 检查是否有危险操作(如命令执行、文件操作等) 检查依赖库 : 检查是否使用了已知存在漏洞的库版本 如Apache Commons Collections、XStream等 七、实验验证 验证 Serializable 接口必要性 移除 implements Serializable 观察序列化失败 验证 transient 关键字效果 添加 transient 修饰符观察字段不被序列化 验证恶意 readObject 方法 添加命令执行代码观察反序列化时执行 八、总结 Java反序列化漏洞是Java安全中的重要议题,其核心在于: 反序列化过程会自动执行 readObject 方法 攻击者可通过构造恶意序列化数据触发危险操作 防御需要从输入控制、类限制、安全编码等多方面入手 理解这些基础知识是进行Java代码审计和漏洞挖掘的重要前提。