浅析Java序列化和反序列化
字数 2035 2025-08-27 12:33:54

Java序列化与反序列化深度解析

序列化基础概念

序列化(Serialization) 是将数据结构或对象状态转换为字节流的过程,以便存储或传输。反序列化(Deserialization) 则是将字节流恢复为原始对象的过程。

Java序列化广泛应用于:

  • EJB (Enterprise JavaBeans)
  • RMI (Remote Method Invocation)
  • Hessian等分布式技术

Java序列化机制详解

基本序列化示例

public class SerializationDemo implements Serializable {
    private String stringField;
    private int intField;
    
    public SerializationDemo(String s, int i) {
        this.stringField = s;
        this.intField = i;
    }
    
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bout);
        out.writeObject(new SerializationDemo("gyyyy", 97777));
    }
}

序列化二进制格式解析

Java序列化结果是一个二进制串,包含以下关键部分:

  1. 魔术头和版本号0xaced0005
  2. 类描述符0x73标识对象类型,0x72标识类描述符
  3. 类名:UTF8格式的类名
  4. serialVersionUID:8字节的唯一标识
  5. 序列化属性标志位:标识对象特性
  6. 字段信息:包括字段类型、名称和值
  7. 父类描述:处理继承关系

序列化执行流程

  1. 初始化ObjectOutputStream,写入魔术头和版本号
  2. 调用writeObject()开始序列化
  3. ObjectStreamClass.lookup()封装类描述信息
  4. writeOrdinaryObject()写入对象数据
  5. writeClassDesc()写入类描述数据
  6. writeSerialData()写入对象的序列化数据

反序列化机制详解

基本反序列化示例

public static void main(String[] args) throws ClassNotFoundException {
    byte[] data; // 从文件或网络获取
    ByteArrayInputStream bin = new ByteArrayInputStream(data);
    ObjectInputStream in = new ObjectInputStream(bin);
    SerializationDemo demo = (SerializationDemo)in.readObject();
}

反序列化执行流程

  1. 初始化ObjectInputStream,校验魔术头和版本号
  2. 调用readObject()开始反序列化
  3. 读取对象类型标识
  4. readClassDesc()读取类描述数据
  5. resolveClass()根据类名获取Class对象
  6. 创建对象实例
  7. readSerialData()读取并填充对象字段数据

serialVersionUID详解

serialVersionUID(SUID)是序列化版本唯一标识,决定反序列化是否成功。计算规则:

  1. 类名(UTF8格式)
  2. 类访问权限标识
  3. 实现的接口名(排序后)
  4. 非私有静态或瞬态字段信息(按字段名排序)
  5. 类初始化器信息
  6. 非私有构造方法信息(按方法签名排序)
  7. 非私有方法信息(按方法名和签名排序)

计算过程使用SHA1算法生成摘要,取前8字节作为SUID。

特殊方法

writeReplace()和readResolve()

  • writeReplace():在序列化前调用,返回实际被序列化的对象
  • readResolve():在反序列化后调用,返回实际反序列化结果对象

Externalizable接口

Serializable的区别:

  • 需要实现readExternal()writeExternal()
  • 必须有无参构造方法
  • 序列化属性标志位为0x0c
  • 字段个数固定为0

反序列化漏洞原理

当服务端反序列化恶意构造的对象时,如果该对象的readObject()方法存在不安全逻辑,可能导致安全问题。

经典漏洞示例:Apache Commons Collections

利用链构造

Transformer[] trans = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod", 
        new Class[]{String.class, Class[].class}, 
        new Object[]{"getRuntime", new Class[0]}),
    new InvokerTransformer("invoke", 
        new Class[]{Object.class, Object[].class}, 
        new Object[]{null, new Object[0]}),
    new InvokerTransformer("exec", 
        new Class[]{String.class}, 
        new Object[]{cmd})
};
Transformer chain = new ChainedTransformer(trans);

触发方式

  1. 使用TransformedMap装饰器触发transform()
  2. 使用AnnotationInvocationHandler作为反序列化载体

POP链构造法则

  1. 命令执行载体选择

    • 使用TemplatesImpl触发newTransformer()getOutputProperties()
    • 使用bsh.Interpreter
  2. 反序列化载体选择

    • HashMap:触发hashCode()equals()
    • AnnotationInvocationHandler:触发setValue()
    • BadAttributeValueExpException:触发toString()
    • PriorityQueue:触发compare()

常见POP链分析

  1. CommonsCollections1-6:使用ChainedTransformer作为命令执行载体
  2. C3P0:通过JNDI注入远程加载恶意对象
  3. Jdk7u21:利用LinkedHashSetAnnotationInvocationHandler
  4. ROME:通过ObjectBean.hashCode()触发
  5. Spring1/2:利用SerializableTypeWrapper

防御方案

  1. 黑名单过滤:过滤危险类
  2. 白名单校验:只允许特定类反序列化
  3. 自定义ObjectInputStream:重写resolveClass()方法进行校验
public class SecureObjectInputStream extends ObjectInputStream {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) 
        throws IOException, ClassNotFoundException {
        // 校验逻辑
    }
}

总结

Java反序列化漏洞的核心在于:

  1. 理解序列化/反序列化机制
  2. 识别可利用的POP链
  3. 掌握常见组件的利用方式
  4. 实施有效的防御措施

反序列化漏洞不仅限于RCE,还可能引发信息泄露、权限提升等多种安全问题。

Java序列化与反序列化深度解析 序列化基础概念 序列化(Serialization) 是将数据结构或对象状态转换为字节流的过程,以便存储或传输。 反序列化(Deserialization) 则是将字节流恢复为原始对象的过程。 Java序列化广泛应用于: EJB (Enterprise JavaBeans) RMI (Remote Method Invocation) Hessian等分布式技术 Java序列化机制详解 基本序列化示例 序列化二进制格式解析 Java序列化结果是一个二进制串,包含以下关键部分: 魔术头和版本号 : 0xaced0005 类描述符 : 0x73 标识对象类型, 0x72 标识类描述符 类名 :UTF8格式的类名 serialVersionUID :8字节的唯一标识 序列化属性标志位 :标识对象特性 字段信息 :包括字段类型、名称和值 父类描述 :处理继承关系 序列化执行流程 初始化 ObjectOutputStream ,写入魔术头和版本号 调用 writeObject() 开始序列化 ObjectStreamClass.lookup() 封装类描述信息 writeOrdinaryObject() 写入对象数据 writeClassDesc() 写入类描述数据 writeSerialData() 写入对象的序列化数据 反序列化机制详解 基本反序列化示例 反序列化执行流程 初始化 ObjectInputStream ,校验魔术头和版本号 调用 readObject() 开始反序列化 读取对象类型标识 readClassDesc() 读取类描述数据 resolveClass() 根据类名获取Class对象 创建对象实例 readSerialData() 读取并填充对象字段数据 serialVersionUID详解 serialVersionUID (SUID)是序列化版本唯一标识,决定反序列化是否成功。计算规则: 类名(UTF8格式) 类访问权限标识 实现的接口名(排序后) 非私有静态或瞬态字段信息(按字段名排序) 类初始化器信息 非私有构造方法信息(按方法签名排序) 非私有方法信息(按方法名和签名排序) 计算过程使用SHA1算法生成摘要,取前8字节作为SUID。 特殊方法 writeReplace()和readResolve() writeReplace() :在序列化前调用,返回实际被序列化的对象 readResolve() :在反序列化后调用,返回实际反序列化结果对象 Externalizable接口 与 Serializable 的区别: 需要实现 readExternal() 和 writeExternal() 必须有无参构造方法 序列化属性标志位为 0x0c 字段个数固定为0 反序列化漏洞原理 当服务端反序列化恶意构造的对象时,如果该对象的 readObject() 方法存在不安全逻辑,可能导致安全问题。 经典漏洞示例:Apache Commons Collections 利用链构造 : 触发方式 : 使用 TransformedMap 装饰器触发 transform() 使用 AnnotationInvocationHandler 作为反序列化载体 POP链构造法则 命令执行载体选择 : 使用 TemplatesImpl 触发 newTransformer() 或 getOutputProperties() 使用 bsh.Interpreter 等 反序列化载体选择 : HashMap :触发 hashCode() 和 equals() AnnotationInvocationHandler :触发 setValue() BadAttributeValueExpException :触发 toString() PriorityQueue :触发 compare() 常见POP链分析 CommonsCollections1-6 :使用 ChainedTransformer 作为命令执行载体 C3P0 :通过JNDI注入远程加载恶意对象 Jdk7u21 :利用 LinkedHashSet 和 AnnotationInvocationHandler ROME :通过 ObjectBean.hashCode() 触发 Spring1/2 :利用 SerializableTypeWrapper 防御方案 黑名单过滤 :过滤危险类 白名单校验 :只允许特定类反序列化 自定义 ObjectInputStream :重写 resolveClass() 方法进行校验 总结 Java反序列化漏洞的核心在于: 理解序列化/反序列化机制 识别可利用的POP链 掌握常见组件的利用方式 实施有效的防御措施 反序列化漏洞不仅限于RCE,还可能引发信息泄露、权限提升等多种安全问题。