什么是 Java 序列化与反序列化
Java 序列化是将内存中的对象转换为字节流的过程,目的是实现对象在不同存储介质(如内存、文件、数据库)或网络间的高效传递 —— 例如网络通信时,发送方需通过序列化将对象转为字节流才能传输;而反序列化则是其逆操作,接收方通过该过程将字节流还原为可直接使用的原始对象。
从技术实现来看,序列化依赖 ObjectOutputStream 类的 writeObject() 方法,反序列化则通过 ObjectInputStream 类的 readObject() 方法完成。值得注意的是,被操作的类必须实现 Serializable 或 Externalizable 接口:
Serializable 是一个标记接口,不含任何方法,仅作为 “允许序列化” 的权限标识;
Externalizable 作为 Serializable 的子类,要求必须重写 writeExternal()(序列化时执行)和 readExternal()(反序列化时执行)方法,可自定义序列化逻辑。
这一机制的应用场景十分广泛,包括 RPC 框架中的跨服务对象传输、对象通过文件或数据库进行持久化存储、分布式系统中不同节点间的对象共享等。
Java 序列化数据结构特点
Java 序列化数据以二进制形式传输,分析或调试时通常以十六进制格式显示。其前四个字节固定,用于标识序列化数据的开始和版本:AC ED 是魔数,标识为 Java 序列化数据;00 05 是版本号,对应 Java 1.5 及以上版本的序列化协议。
Java 序列化与反序列化代码示例
基础类定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.lang.serializable.pojo; import java.io.Serializable; public class People implements Serializable { private static final Long serialVersionUID = 60L; private String name; public People(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "People{" + "name='" + name + '\'' + '}'; } }
|
序列化操作
1 2 3 4 5 6 7 8 9
| @Test public void test01() throws IOException { People jack = new People("Jack"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./obj.txt")); oos.writeObject(jack); oos.close(); }
|
反序列化操作
1 2 3 4 5 6 7 8
| @Test public void test02() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./obj.txt")); People people = (People) ois.readObject(); System.out.println(people); ois.close(); }
|
[Java 反序列化]漏洞原理及触发条件
漏洞本质
当反序列化过程处理攻击者构造的恶意字节流时,可能触发危险操作,如命令执行、代码注入等。
触发关键点
- 危险方法重写:类重写 readObject () 或 readExternal () 时,若包含未校验的危险操作(如 Runtime.getRuntime ().exec ()),可能被利用。
- 输入校验缺失:开发者未对反序列化的字节流来源和内容进行严格校验,导致恶意对象被解析。
重写序列化方法的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.lang.serializable.pojo; import java.io.*; public class People implements Serializable { private static final Long serialVersionUID = 60L; private String name; private String pwd; @Override public String toString() { return "People{" + "name='" + name + "', pwd='" + pwd + '\'' + '}'; } private void writeObject(ObjectOutputStream out) throws IOException { ObjectOutputStream.PutField putField = out.putFields(); putField.put("name", name); out.writeFields(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { ObjectInputStream.GetField getField = in.readFields(); name = (String) getField.get("name", "rose"); } }
|