初识Java反序列化
字数 2225 2025-08-12 12:46:02

Java反序列化原理与漏洞分析

一、序列化与反序列化基础

1.1 基本概念

序列化:将Java对象转化为字节序列并存储在文件系统中的过程,使用ObjectOutputStream.writeObject()方法。

反序列化:将存储在文件系统的字节序列转化成Java对象的过程,使用ObjectInputStream.readObject()方法。

1.2 实现要求

要使类可序列化,必须满足:

  • 实现java.io.Serializable接口
  • 所有成员变量必须是可序列化的或标记为transient

1.3 示例代码

Employ类(可序列化类)

import java.io.Serializable;

public class Employ implements Serializable {
    public String name;
    public int age;
}

序列化示例(test.java)

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

public class test {
    public static void main(String[] args) {
        Employ e = new Employ();
        e.name = "zhangyida";
        e.age = 15;
        
        try {
            FileOutputStream fops = new FileOutputStream("/tmp/1.ser");
            ObjectOutputStream obos = new ObjectOutputStream(fops);
            obos.writeObject(e);
            obos.close();
            System.out.println("Serialized data is saved in /tmp/1.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

反序列化示例(desEmploy.java)

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

public class desEmploy {
    public static void main(String[] args) {
        Employ e = null;
        
        try {
            FileInputStream fis = new FileInputStream("/tmp/1.ser");
            ObjectInputStream obis = new ObjectInputStream(fis);
            e = (Employ) obis.readObject();
            obis.close();
            fis.close();
        } catch (IOException i) {
            i.printStackTrace();
            return;
        } catch (ClassNotFoundException ex) {
            System.out.println("Employ class not found!");
            ex.printStackTrace();
            return;
        }
        
        System.out.println(e.name);
        System.out.println(e.age);
    }
}

二、序列化数据格式

2.1 字节序列结构

Java序列化后的字节序列由三部分组成:

  1. Magic Number:固定值0xACED(标识Java序列化流)
  2. Version Number:固定值0x0005(版本号)
  3. Contents:实际序列化内容

2.2 Contents组成

Contents可能包含一个或多个content,每个content由以下部分组成:

  • Object:序列化对象
  • BlockData:数据块

2.3 对象类型

序列化流中常见的对象类型:

类型 标识符 描述
newObject TC_OBJECT 普通对象
newClassDesc TC_CLASSDESC 类描述对象
newString TC_STRING 字符串对象
newArray TC_ARRAY 数组对象
newEnum TC_ENUM 枚举对象
prevObject TC_REFERENCE 对之前对象的引用
nullReference TC_NULL 空引用
exception TC_EXCEPTION 异常对象
TC_RESET TC_RESET 重置标记

2.4 newObject结构

newObject:
    TC_OBJECT
    classDesc
    newHandle
    classdata[]
  • classDesc:ObjectStreamClass对象,包含类名、序列化ID、类字段等信息
  • newHandle:对象句柄值(类似对象ID)
  • classdata[]:类实例化对象属性

2.5 newClassDesc结构

newClassDesc:
    TC_CLASSDESC
    className
    serialVersionUID
    newHandle
    classDescInfo

classDescInfo结构

classDescInfo:
    classDescFlags
    fields
    classAnnotation
    superClassDesc
  • classDescFlags
    • 0x02:SC_SERIALIZABLE(实现了Serializable接口但未重写readObject)
    • 0x03:SC_WRITE_METHOD | SC_SERIALIZABLE(实现了Serializable且重写了readObject)
  • fields:类的属性
  • classAnnotation:类注解(通常为TC_ENDBLOCKDATA)
  • superClassDesc:父类的classDesc(若可序列化)或TC_NULL(若不可序列化)

三、序列化使用场景

  1. 持久化存储:将对象状态保存到文件系统,如用户session信息
  2. 网络传输
    • JNDI (Java Naming and Directory Interface)
    • RMI (Remote Method Invocation)
  3. 数据交换格式
    • XML (Xstream, XMLDecoder)
    • JSON (Jackson, fastjson)
  4. 缓存系统:如Redis等分布式缓存

四、反序列化漏洞原理

4.1 漏洞成因

当满足以下条件时,可能产生反序列化漏洞:

  1. 类重写了readObject方法
  2. 重写的readObject方法中存在可执行危险代码的逻辑
  3. 攻击者能够控制输入的反序列化数据

4.2 漏洞示例

恶意Employ类

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

public class Employ implements Serializable {
    public String name;
    
    private void readObject(ObjectInputStream objin) {
        try {
            objin.defaultReadObject();
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

当此类的序列化数据被反序列化时,会执行系统命令打开计算器。

4.3 Gadget Chains

在实际漏洞利用中,通常需要:

  1. 找到重写了readObject方法的可利用类(称为gadget
  2. 构造调用链(gadget chain)最终执行危险操作
  3. 常见危险操作包括:
    • Runtime.getRuntime().exec()
    • ProcessBuilder.start()
    • 反射调用危险方法
    • JNDI注入等

五、防御措施

  1. 输入验证:不要反序列化不受信任的数据
  2. 使用白名单:限制可反序列化的类
  3. 安全配置
    • 使用ObjectInputFilter设置反序列化过滤器
    • 在Java 9+中使用jdk.serialFilter系统属性
  4. 替代方案:考虑使用JSON等更安全的序列化格式
  5. 代码审计:检查自定义readObject方法的安全性

六、分析工具

  1. SerializationDumper:解析Java序列化数据,显示详细结构
  2. ysoserial:生成各种gadget chains用于测试
  3. Burp Suite Java Serialized Payloads插件:用于Web应用测试

通过深入理解Java序列化机制及其安全风险,开发人员和安全研究人员可以更好地防御和发现相关漏洞。

Java反序列化原理与漏洞分析 一、序列化与反序列化基础 1.1 基本概念 序列化 :将Java对象转化为字节序列并存储在文件系统中的过程,使用 ObjectOutputStream.writeObject() 方法。 反序列化 :将存储在文件系统的字节序列转化成Java对象的过程,使用 ObjectInputStream.readObject() 方法。 1.2 实现要求 要使类可序列化,必须满足: 实现 java.io.Serializable 接口 所有成员变量必须是可序列化的或标记为 transient 1.3 示例代码 Employ类(可序列化类) : 序列化示例(test.java) : 反序列化示例(desEmploy.java) : 二、序列化数据格式 2.1 字节序列结构 Java序列化后的字节序列由三部分组成: Magic Number :固定值 0xACED (标识Java序列化流) Version Number :固定值 0x0005 (版本号) Contents :实际序列化内容 2.2 Contents组成 Contents可能包含一个或多个content,每个content由以下部分组成: Object :序列化对象 BlockData :数据块 2.3 对象类型 序列化流中常见的对象类型: | 类型 | 标识符 | 描述 | |------|--------|------| | newObject | TC_ OBJECT | 普通对象 | | newClassDesc | TC_ CLASSDESC | 类描述对象 | | newString | TC_ STRING | 字符串对象 | | newArray | TC_ ARRAY | 数组对象 | | newEnum | TC_ ENUM | 枚举对象 | | prevObject | TC_ REFERENCE | 对之前对象的引用 | | nullReference | TC_ NULL | 空引用 | | exception | TC_ EXCEPTION | 异常对象 | | TC_ RESET | TC_ RESET | 重置标记 | 2.4 newObject结构 classDesc :ObjectStreamClass对象,包含类名、序列化ID、类字段等信息 newHandle :对象句柄值(类似对象ID) classdata[] :类实例化对象属性 2.5 newClassDesc结构 classDescInfo结构 : classDescFlags : 0x02 :SC_ SERIALIZABLE(实现了Serializable接口但未重写readObject) 0x03 :SC_ WRITE_ METHOD | SC_ SERIALIZABLE(实现了Serializable且重写了readObject) fields :类的属性 classAnnotation :类注解(通常为TC_ ENDBLOCKDATA) superClassDesc :父类的classDesc(若可序列化)或TC_ NULL(若不可序列化) 三、序列化使用场景 持久化存储 :将对象状态保存到文件系统,如用户session信息 网络传输 : JNDI (Java Naming and Directory Interface) RMI (Remote Method Invocation) 数据交换格式 : XML (Xstream, XMLDecoder) JSON (Jackson, fastjson) 缓存系统 :如Redis等分布式缓存 四、反序列化漏洞原理 4.1 漏洞成因 当满足以下条件时,可能产生反序列化漏洞: 类重写了 readObject 方法 重写的 readObject 方法中存在可执行危险代码的逻辑 攻击者能够控制输入的反序列化数据 4.2 漏洞示例 恶意Employ类 : 当此类的序列化数据被反序列化时,会执行系统命令打开计算器。 4.3 Gadget Chains 在实际漏洞利用中,通常需要: 找到重写了 readObject 方法的可利用类(称为 gadget ) 构造调用链( gadget chain )最终执行危险操作 常见危险操作包括: Runtime.getRuntime().exec() ProcessBuilder.start() 反射调用危险方法 JNDI注入等 五、防御措施 输入验证 :不要反序列化不受信任的数据 使用白名单 :限制可反序列化的类 安全配置 : 使用 ObjectInputFilter 设置反序列化过滤器 在Java 9+中使用 jdk.serialFilter 系统属性 替代方案 :考虑使用JSON等更安全的序列化格式 代码审计 :检查自定义 readObject 方法的安全性 六、分析工具 SerializationDumper :解析Java序列化数据,显示详细结构 ysoserial :生成各种gadget chains用于测试 Burp Suite Java Serialized Payloads插件 :用于Web应用测试 通过深入理解Java序列化机制及其安全风险,开发人员和安全研究人员可以更好地防御和发现相关漏洞。