Java原生反序列化漏洞
字数 1684 2025-08-11 08:35:50

Java原生反序列化漏洞深入解析

第一章:序列化基础

1.1 Serializable与Externalizable接口

Java提供了两种序列化机制:

public interface Serializable { }  // 标记接口
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

序列化方式对比

特性 Serializable Externalizable
序列化方式 默认序列化非transient和非static属性 需重写writeExternal和readExternal方法
序列号 需要serialVersionUID 不需要
实现方式 反射机制,无需无参构造 需要无参构造方法
自定义序列化 可定义readObject和writeObject 完全自定义实现

1.2 序列化关键点

  • Serializable:采用反射机制完成内容恢复,没有无参构造函数的限制
  • Externalizable:必须有无参构造方法,否则反序列化会报错

第二章:反序列化漏洞原理

2.1 反序列化流程

Java原生反序列化的核心流程:

  1. readObject()readObject(Object.class)readObject0()
  2. readObject0()中根据流类型进入不同分支(TC_OBJECT、TC_ARRAY等)
  3. 对于普通对象,进入readOrdinaryObject()方法
  4. readOrdinaryObject()调用readClassDesc()获取类描述符
  5. 通过类描述符实例化对象
  6. 调用readSerialData()恢复对象数据

2.2 关键方法分析

readOrdinaryObject()

private Object readOrdinaryObject(boolean unshared) throws IOException {
    // 读取类描述符
    ObjectStreamClass desc = readClassDesc(false);
    // 实例化对象
    Object obj = desc.isInstantiable() ? desc.newInstance() : null;
    // 恢复对象数据
    readSerialData(obj, desc);
    return obj;
}

readSerialData()

private void readSerialData(Object obj, ObjectStreamClass desc) {
    // 检查是否有自定义readObject方法
    if (slotDesc.hasReadObjectMethod()) {
        // 调用自定义readObject方法
        slotDesc.invokeReadObject(obj, this);
    } else {
        // 默认反序列化流程
        defaultReadFields(obj, slotDesc);
    }
}

invokeReadObject()

void invokeReadObject(Object obj, ObjectInputStream in) {
    // 通过反射调用自定义readObject方法
    readObjectMethod.invoke(obj, new Object[]{ in });
}

第三章:漏洞利用分析

3.1 POC示例

package org.demo1;

import java.io.*;

public class Deserialize {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String path = "C:\\Users\\Lenovo\\Desktop\\1.txt";
        Person person = new Person("ceshi", 22);
        
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(person);
        oos.close();
        
        // 反序列化 - 漏洞触发点
        FileInputStream fis = new FileInputStream(path);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
        ois.close();
    }
    
    public static class Person implements Serializable {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        // 恶意readObject方法
        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
            Runtime.getRuntime().exec("calc.exe");
        }
    }
}

3.2 漏洞触发流程

  1. 反序列化时调用ois.readObject()
  2. 进入readObject0()方法,识别为TC_OBJECT类型
  3. 调用readOrdinaryObject()处理普通对象
  4. 通过readClassDesc()获取类描述符,其中包含自定义readObject方法
  5. 实例化对象后,在readSerialData()中检测到有自定义readObject方法
  6. 通过反射调用恶意readObject方法,执行任意代码

第四章:防御措施

4.1 安全建议

  1. 输入验证:不要反序列化不受信任的数据
  2. 使用白名单:实现ObjectInputFilter限制可反序列化的类
  3. 替代方案:使用JSON、XML等更安全的序列化格式
  4. 安全管理器:配置Java安全策略限制敏感操作
  5. 更新JDK:使用最新版本JDK,修复已知漏洞

4.2 代码示例 - 使用ObjectInputFilter

ObjectInputStream ois = new ObjectInputStream(fis);
ois.setObjectInputFilter(filterInfo -> {
    if (!filterInfo.serialClass().getName().equals("org.demo1.Person")) {
        return ObjectInputFilter.Status.REJECTED;
    }
    return ObjectInputFilter.Status.ALLOWED;
});
ois.readObject();

第五章:深入技术细节

5.1 类描述符(ObjectStreamClass)机制

  • lookup()方法查找并返回给定类的类描述符
  • 类描述符中包含:
    • 序列化版本UID
    • 字段信息
    • 自定义readObject/writeObject方法
    • readResolve/writeReplace方法

5.2 反序列化关键数据结构

  • TC_OBJECT:表示新对象
  • TC_CLASSDESC:表示新类描述符
  • TC_REFERENCE:表示对先前序列化对象的引用

5.3 漏洞利用扩展

除了直接实现readObject方法外,攻击者还可能利用:

  1. JDK内置类的危险方法(如InvokerTransformer
  2. 第三方库中的危险类(如commons-collections)
  3. 通过反射链构造复杂的攻击载荷

总结

Java原生反序列化漏洞的核心在于反序列化过程中自动调用自定义readObject方法的机制。攻击者可以通过构造恶意序列化对象,在目标系统上执行任意代码。理解反序列化的完整流程和关键方法对于防御此类漏洞至关重要。

Java原生反序列化漏洞深入解析 第一章:序列化基础 1.1 Serializable与Externalizable接口 Java提供了两种序列化机制: 序列化方式对比 | 特性 | Serializable | Externalizable | |------|------------|--------------| | 序列化方式 | 默认序列化非transient和非static属性 | 需重写writeExternal和readExternal方法 | | 序列号 | 需要serialVersionUID | 不需要 | | 实现方式 | 反射机制,无需无参构造 | 需要无参构造方法 | | 自定义序列化 | 可定义readObject和writeObject | 完全自定义实现 | 1.2 序列化关键点 Serializable :采用反射机制完成内容恢复,没有无参构造函数的限制 Externalizable :必须有无参构造方法,否则反序列化会报错 第二章:反序列化漏洞原理 2.1 反序列化流程 Java原生反序列化的核心流程: readObject() → readObject(Object.class) → readObject0() 在 readObject0() 中根据流类型进入不同分支(TC_ OBJECT、TC_ ARRAY等) 对于普通对象,进入 readOrdinaryObject() 方法 readOrdinaryObject() 调用 readClassDesc() 获取类描述符 通过类描述符实例化对象 调用 readSerialData() 恢复对象数据 2.2 关键方法分析 readOrdinaryObject() readSerialData() invokeReadObject() 第三章:漏洞利用分析 3.1 POC示例 3.2 漏洞触发流程 反序列化时调用 ois.readObject() 进入 readObject0() 方法,识别为TC_ OBJECT类型 调用 readOrdinaryObject() 处理普通对象 通过 readClassDesc() 获取类描述符,其中包含自定义 readObject 方法 实例化对象后,在 readSerialData() 中检测到有自定义 readObject 方法 通过反射调用恶意 readObject 方法,执行任意代码 第四章:防御措施 4.1 安全建议 输入验证 :不要反序列化不受信任的数据 使用白名单 :实现 ObjectInputFilter 限制可反序列化的类 替代方案 :使用JSON、XML等更安全的序列化格式 安全管理器 :配置Java安全策略限制敏感操作 更新JDK :使用最新版本JDK,修复已知漏洞 4.2 代码示例 - 使用ObjectInputFilter 第五章:深入技术细节 5.1 类描述符(ObjectStreamClass)机制 lookup() 方法查找并返回给定类的类描述符 类描述符中包含: 序列化版本UID 字段信息 自定义 readObject / writeObject 方法 readResolve / writeReplace 方法 5.2 反序列化关键数据结构 TC_ OBJECT :表示新对象 TC_ CLASSDESC :表示新类描述符 TC_ REFERENCE :表示对先前序列化对象的引用 5.3 漏洞利用扩展 除了直接实现 readObject 方法外,攻击者还可能利用: JDK内置类的危险方法(如 InvokerTransformer ) 第三方库中的危险类(如commons-collections) 通过反射链构造复杂的攻击载荷 总结 Java原生反序列化漏洞的核心在于反序列化过程中自动调用自定义 readObject 方法的机制。攻击者可以通过构造恶意序列化对象,在目标系统上执行任意代码。理解反序列化的完整流程和关键方法对于防御此类漏洞至关重要。