Java序列化与反序列化
0x00 概述
Java序列化是指把Java对象转换为字节序列的过程
Java反序列化是指把字节序列恢复为Java对象的过程
0x01 为什么需要序列化和反序列化
当两个Java进程进行通信时,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;接收方需要从字节序列中恢复出Java对象。
0x02 序列化和反序列化的作用
- 数据持久化:
将对象转换为字节流后,可以将字节流保存到磁盘或数据库中,实现数据的持久化。在下次需要时,可以从存储介质中读取字节流并进行反序列化,重新得到原始对象。
- 远程通信:
在分布式系统中,不同的计算节点之间需要进行数据的传输和共享。通过序列化和反序列化,可以将对象转换为可传输的字节流,在网络上进行传输,并在接收端反序列化为对象。
- 缓存机制:
在缓存技术中,可以通过将对象序列化后存储在缓存中,下次需要时直接从缓存中读取并反序列化为对象,提高数据的读取效率。
- 对象复制和深拷贝:
有时需要对对象进行复制或深拷贝,在内存中创建一个与原始对象完全相同的新对象。通过序列化和反序列化,可以实现对象的深度复制,即创建一个与原始对象相互独立的副本。
- 分布式计算和集群:
在分布式计算和集群环境中,任务可以在不同的节点上并行执行。通过序列化和反序列化,可以将任务对象传输到具体的执行节点,以便执行远程调用。
0x03 序列化和反序列化的实现
实现序列化和反序列话的条件
只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列,不然会抛出异常。
- Serializable 接口
Serializable 是 Java 中的一个接口,它是一个标记接口,不包含任何方法。
1 | public interface Serializable { |
示例
- TestPerson类
1 | public class TestPerson implements Serializable { |
- 对TestPerson类进行序列化操作,并将生成的字节序列写入 ser.bin 文件中
1 | public class TestSerializable { |
- 从序列化的字节序列中恢复 TestPerson对象
1 | public class TestUnserialize { |
transient 修饰符
- 当一个成员变量被
transient修饰时,它将不会被默认的序列化机制序列化。 - 当对象被序列化时,被
transient修饰的字段会被忽略,并且在反序列化过程中会被赋予默认值(例如数值类型为0,引用类型为null) transient只对对象的序列化有效,并不影响对象的其他行为和方法调用
1 | public class TestPerson implements Serializable { |
- TestSerialize
1 | 输出结果: |
- TestUnserialize
1 | 输出结果: |
0x04 为什么会产生安全问题
只要服务器反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
可能存在的形式
- 入口类的readObject直接调用危险方法。
HashMap<Object,Object>、Hashtable<Object,Object> ……
- 入口类参数中包含可控类,该类有危险方法,readObject时调用
- 入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用
0x05 重写writeObject和readObject
开发者可以重写 writeObject和readObject 方法,这样在序列化/反序列化时,系统会调用自定义的 writeObject和readObject 方法而不是使用默认的方法。