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原生反序列化的核心流程:
readObject()→readObject(Object.class)→readObject0()- 在
readObject0()中根据流类型进入不同分支(TC_OBJECT、TC_ARRAY等) - 对于普通对象,进入
readOrdinaryObject()方法 readOrdinaryObject()调用readClassDesc()获取类描述符- 通过类描述符实例化对象
- 调用
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 漏洞触发流程
- 反序列化时调用
ois.readObject() - 进入
readObject0()方法,识别为TC_OBJECT类型 - 调用
readOrdinaryObject()处理普通对象 - 通过
readClassDesc()获取类描述符,其中包含自定义readObject方法 - 实例化对象后,在
readSerialData()中检测到有自定义readObject方法 - 通过反射调用恶意
readObject方法,执行任意代码
第四章:防御措施
4.1 安全建议
- 输入验证:不要反序列化不受信任的数据
- 使用白名单:实现
ObjectInputFilter限制可反序列化的类 - 替代方案:使用JSON、XML等更安全的序列化格式
- 安全管理器:配置Java安全策略限制敏感操作
- 更新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方法外,攻击者还可能利用:
- JDK内置类的危险方法(如
InvokerTransformer) - 第三方库中的危险类(如commons-collections)
- 通过反射链构造复杂的攻击载荷
总结
Java原生反序列化漏洞的核心在于反序列化过程中自动调用自定义readObject方法的机制。攻击者可以通过构造恶意序列化对象,在目标系统上执行任意代码。理解反序列化的完整流程和关键方法对于防御此类漏洞至关重要。