Java反序列化
序列化与反序列化
序列化是从 java 对象转换为字节序列的过程
反序列化是把字节序列恢复为 java 对象的过程
为什么序列化
对象不只是存储这内存中,还需要这传输网络进行传输,保存起来之后下次再加载出来,这是需要序列化技术,java 的序列化技术是把对象转换成由二进制字节组成的数组,然后将二进制数据保存再磁盘或传输网络,然后需要用到对象时,磁盘或者网络接受者可以通过反序列化得到对象,达到对象持久化目的
ObjectOutputStream 与 ObjectInputStream 类
ObjectOutputStream
java.io.ObjectOutputStream 类,将 java 对象的原始数据类型写出到文件,实现对象的持久存储
序列化操作
一个对象想序列化 必须满足:
该类必须实现 java.io.Serializable 接口 Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException
该类所有属性必须是可序列化的,如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用 transient 关键字修饰
示例:
Employee.java
public classEmployee implements java.io.Serializable{
public String name;
public String address;
public transient int age;//transient瞬态修饰成员 不会被序列化
public void addressCheck() {
System.out.println("Address check : " + bane + " -- " +address);
//此处省略tostring方法
}
}
SerializeDemo.java
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 Fileoutputstram("ser.txt"));
//写出对象
outputstream.writeobject(e);
//释放资源
outputstream.close();
}
}
将 Employee 对象写入到 employee.txt 文件中
开头的 AC ED 00 05 为序列化内容特征
ObjectInputStream
如果能找到一个对象的 class 文件,可以进行反序列化操作,调用 objectinputstream 读取对象的方法
public class DeserializeDemo{
public static void main (String[] args)throws IOException,ClassNotFoundException{
//创建反序列化流
FileInputStream fileInputStream = new FileInputStream("ser.txt");
ObjectInputStream inputstream = new objectinputstream(fileinputstream);
//使用objectinputstream中的readobject读取一个对象
object o = inputSteam.readobject();
//释放资源
inputStream.close();
System.out.println(o);
}
}
反序列化操作就是从二进制文件中提取对象
反序列化漏洞原理
当一个应用使用ObjectInputStream.readObject()方法从外部输入(如网络连接)读取对象时,如果这个对象是恶意构造的,那么在反序列化过程中可能会触发一些特殊的行为,比如执行特定的方法或改变某些变量值等。这些行为可能包括但不限于:
- 执行系统命令
- 修改应用程序配置
- 访问敏感资源
- 进行拒绝服务攻击
关键点:
- Java的序列化机制不仅保存了对象的数据,还保存了类的信息。这意味着在反序列化时,会调用到原始对象定义的一些方法。
- 如果存在可被利用的类(例如Apache Commons Collections库中的某些类),并且这些类实现了
Serializable接口,则它们可以通过精心构造的数据来达到攻击目的。
例子
假设有一个简单的类User实现了Serializable接口,并且有一个readResolve()方法:
public class User implements Serializable {
private String name;
// 默认构造函数
public User() {}
// 构造函数
public User(String name) {
this.name = name;
}
// readResolve 方法
protected Object readResolve() throws ObjectStreamException {
if ("admin".equals(this.name)) {
return new User("guest");
}
return this;
}
}
正常情况下,即使尝试创建一个名为"admin"的用户,readResolve()方法也会将其更改为"guest"。但如果攻击者能够控制输入流,他们可能会绕过此检查,或者找到其他方式来执行恶意代码。
如何防止
- 避免直接使用
readObject():尽量不要直接从不可信源接收和反序列化对象。 - 白名单机制:只允许特定类型的对象进行反序列化。
- 使用安全库:例如Apache Commons Lang提供的
SerializationUtils,它提供了更安全的方式来处理序列化/反序列化。 - 更新依赖库:确保所有使用的第三方库都是最新版本,以包含最新的安全修复。
- 自定义反序列化器:实现自己的反序列化逻辑,增加额外的安全检查。
总之,理解和识别潜在的风险是保护应用程序免受此类攻击的关键步骤之一。正确地管理和限制对序列化数据的操作可以帮助显著减少这类安全问题的发生。
更新: 2025-05-10 11:21:57
原文: https://www.yuque.com/yuhui.net/network/fdcebcp8ag32ps14

评论(0)
暂无评论