java代码审计之反序列化基础1
字数 1842 2025-08-19 12:42:22
Java反序列化基础教学文档
一、Java反序列化概念
1. 序列化与反序列化定义
- 序列化:将对象转换为字节序列的过程
- 目的:将数据转化成字节流,以便存储在文件中或在网络上传输
- 反序列化:把字节序列恢复为对象的过程
- 目的:打开字节流并重构对象
2. 为什么需要序列化/反序列化
当两个Java进程进行通信时,需要实现进程间的对象传输,这时就需要:
- 发送方将对象序列化为字节流
- 通过网络传输字节流
- 接收方将字节流反序列化为对象
二、序列化实现基础
1. 可序列化条件
- 类必须实现
java.io.Serializable接口Serializable是一个标记接口(空接口,无抽象方法)- 未实现该接口的类尝试序列化会抛出
java.io.NotSerializableException
2. 阻止序列化的方法
- 使用
transient关键字修饰属性- 被
transient修饰的属性不会被序列化 - 反序列化时,
transient字段会被设为默认值(如String为null,int为0)
- 被
三、代码实现详解
1. 基础类定义(Person.java)
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.Runtime;
public class Person implements Serializable {
private String name;
private int age;
public Person(){}
// 构造函数
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "Person{" + "name='" + name + "', age=" + age + '}';
}
}
2. 序列化示例(SerializationTest.java)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("ser.bin"));
oos.writeObject(obj);
System.out.println(oos);
}
public static void main(String[] args) throws Exception{
Person person = new Person("aa",22);
serialize(person);
}
}
关键代码解析:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));- 创建
ObjectOutputStream对象,用于将对象序列化成字节流 - 写入名为"ser.bin"的文件(不存在则创建)
- 创建
oos.writeObject(obj);- 将传入的对象写入输出流,实现序列化
3. 反序列化示例(UnserializeTest.java)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
Person person = (Person)unserialize("ser.bin");
System.out.println(person);
}
}
关键代码解析:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));- 创建
ObjectInputStream对象,用于从文件读取字节流并反序列化
- 创建
Object obj = ois.readObject();- 从输入流中读取对象字节流并反序列化为Java对象
四、安全风险与漏洞原理
1. 漏洞根源
- Java反序列化漏洞的根本原因与
readObject方法有关 - 在反序列化过程中,
readObject方法中的代码会自动执行
2. 危险示例
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化逻辑
Runtime.getRuntime().exec("calc"); // 恶意代码
}
- 当反序列化包含上述
readObject方法的对象时:- 先执行默认反序列化逻辑(
defaultReadObject) - 然后执行
Runtime.getRuntime().exec("calc"),启动计算器
- 先执行默认反序列化逻辑(
3. 漏洞利用条件
- 攻击者能够控制反序列化的数据源
- 目标应用中存在可被利用的类(包含危险方法的类)
- 这些类在应用的classpath中
五、防御措施
- 输入验证:不要反序列化不受信任的数据
- 使用白名单:限制可反序列化的类
- 使用替代方案:
- 使用JSON、XML等更安全的序列化格式
- 使用第三方安全库如SerialKiller
- 更新依赖:及时修复已知漏洞的库(如Apache Commons Collections)
- 安全管理器:配置适当的安全策略
六、审计要点
-
查找反序列化入口点:
ObjectInputStream.readObject()调用- 网络通信、文件读取等可能接收外部输入的地方
-
检查可序列化类:
- 检查
readObject、readResolve等方法实现 - 检查是否有危险操作(如命令执行、文件操作等)
- 检查
-
检查依赖库:
- 检查是否使用了已知存在漏洞的库版本
- 如Apache Commons Collections、XStream等
七、实验验证
-
验证
Serializable接口必要性- 移除
implements Serializable观察序列化失败
- 移除
-
验证
transient关键字效果- 添加
transient修饰符观察字段不被序列化
- 添加
-
验证恶意
readObject方法- 添加命令执行代码观察反序列化时执行
八、总结
Java反序列化漏洞是Java安全中的重要议题,其核心在于:
- 反序列化过程会自动执行
readObject方法 - 攻击者可通过构造恶意序列化数据触发危险操作
- 防御需要从输入控制、类限制、安全编码等多方面入手
理解这些基础知识是进行Java代码审计和漏洞挖掘的重要前提。