初识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序列化后的字节序列由三部分组成:
- 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结构
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(若不可序列化)
三、序列化使用场景
- 持久化存储:将对象状态保存到文件系统,如用户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类:
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
在实际漏洞利用中,通常需要:
- 找到重写了
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序列化机制及其安全风险,开发人员和安全研究人员可以更好地防御和发现相关漏洞。