0x01 前言
在Java中实现对象反序列化非常简单
只需要继承并实现 java.io.Serializable 或是 java.io.Externalizable 接口即可被序列化
其中 java.io.Externalizable 只是实现了 java.io.Serializable 的接口
但是两者还是有区别的,区别如下:
注意: 复制了P牛的小圈子一个大佬发的结论,比我自己总结的更好, 总的来说,有点小小修改
- 实现
Externalizable, 序列化过程需要开发人员自己实现。未实现writeExternal()和readExternal(),则序列化时不会保存和读取任何字段。属性使用和不使用 transient 修饰,无任何区别 Externalizable优先级比Serializable更高如果在序列化的时候使用了构造方法的情况下,那
Externalizable必须要创建默认的无参构造方法,Serializable方法可以没有默认的无参构造方法java.io.Externalizable的例子讲解,下一篇在讲, 本文请聚焦在java.io.Serializable
其中第三点,需要在在在注意一下,使用 Externalizable 的时候:
如果你在序列化的时候没有使用构造方法!!!!!
那么是可以不创建无参构造方法也能成功反序列化的
但是如果你使用了构造方法来进行序列化!!!!!
那么则必须创建一个无参构造方法,这样才能成功的反序列化
反序列化类对象时有如下限制:
- 被反序列化的类必须存在
serialVersionUID值必须一致
注意: 修改,添加,删除被序列化类的方法或是属性都会导致 serialVersionUID 值的变化
另外需要注意的一点:
反序列化时类对象是不会调用该类构造方法的!!!!!
因为反序列化时是使用的 sun.reflect.ReflectionFactory.newConstructorForSerialization 来创建了一个反序列化专用的 Constructor, 接着使用这个 Constructor 来绕过构造方法创建类实例
当然还有另外一种绕过 Constructor 构造方法创建类实例可以查看前面的章节Java速记->第二章-Java 安全基础->3. Java sun.misc.Unsafe的妙用进行学习查看
0x02 重点的类方法
java.io.ObjectOutputStream 类最核心的方法就是 writeObject()方法,即序列化类对象java.io.ObjectInputStream 类最核心的方法就是 readObject()方法,即反序列化类对象因此只需借助 ObjectOutputStream 与 ObjectInputStream 类,即可实现类的序列化和反序列化功能而其中:java.io.ObjectOutputStream 类,有几个常用方法分别是writeObject() # 自定义序列化类对象,类被序列化时调用writeReplace() # 写入替换对象时触发,类被序列化时调用注意: writeObject() 方法是漏洞造成的重灾区之一, 因此需要认真学习并理解该方法java.io.ObjectInputStream 类,有几个常用方法分别是readObject() # 自定义反序列化类对象,反序列化时调用readResolve() # 读取对象时触发readObjectNoData() # 当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本readUnshared() # 此方法与readObject()相同,不同之处在于它阻止对readObject和readUnshared的后续调用返回对通过此调用获得的反序列化实例的额外引用注意: readObject() 方法是漏洞造成的重灾区之一, 因此需要认真学习并理解该方法
0x03 java.io.Serializable 接口作用
java.io.Serializable 如果跟进去你就会发现,这是一个空接口,开发者无需实现里面的任何方法
代码如下:
public interface Serializable {}
继承并实现 java.io.Serializable 这个接口的作用就是表明这个类可序列化
并且会通过这个类的方法与变量计算出来一个 serialVersionUID 常量
这个serialVersionUID是用来验证版本是否一致的
在进行反序列化时, JVM会把传进来的字节流中的 serialVersionUID 与本地相应实体类的serialVersionUID进行比较
如果有变化serialVersionUID就会不同, 导致InvalidClassException异常, 否则就进行反序列化
注意: serialVersionUID 是可以自己创建的,但是一般都不会自己去创建
创建 serialVersionUID 的例子例如:
# 格式public class Person implements Serializable {# 喜欢多长自己写# 注意: 这个类型必须为longprivate static final long serialVersionUID = xL;}# 实例public class Person implements Serializable {private static final long serialVersionUID = 2239109261752820180L;}
0x04 例子
以下的例子主要是让我们知道这些方法在什么时候会被调用,心里有个大概的数,这样子方便理解后面的关于反序列化链子的文章
0x04.1 java.io.ObjectOutputStream 类
0x04.1.1 writeObject() - 重点方法
// 第一步创建 SerializeTest1 类package java反序列化测试;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;public class SerializeTest1 implements Serializable {/*** 自定义序列化类对象* 调用: 类被序列化时调用* 注意: writeReplace() 与 writeObject() 同时存在时,执行 writeReplace()** @param oos 序列化输出流对象* @throws IOException IO异常*/private void writeObject(ObjectOutputStream oos) throws IOException {System.out.println("writeObject...");// ObjectOutputStream 默认反序列化方法oos.defaultWriteObject();}}
// 第二步-序列化 SerializeTest1 类package java反序列化测试;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class SerializeDemo1 {public static void main(String[] args) {// SerializeTest1 序列化SerializeTest1 e = new SerializeTest1();try {FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/SerializeTest1.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();} catch (IOException i) {i.printStackTrace();}}}// 运行结果:// 输出: writeObject...// 并且会在在 /src/main/java/java反序列化测试/ 生成一个 SerializeTest1.ser 文件
0x04.1.2 writeReplace()
// 第一步创建 SerializeTest2 类package java反序列化测试;import java.io.Serializable;import java.util.ArrayList;public class SerializeTest2 implements Serializable {/*** 写入替换对象时触发* 调用: 类被序列化时调用* 注意: writeReplace() 与 writeObject() 同时存在时,执行 writeReplace()* 启动这个以后获取的内容只有 list** @return 替换后的对象*/private Object writeReplace() {System.out.println("writeReplace....");ArrayList<Object> list = new ArrayList<>();list.add("test1");list.add("test2");return list;}}
// 第二步-序列化 SerializeTest2 类// 并且反序列化获取 writeReplace() 方法,返回的数据package java反序列化测试;import java.io.*;public class SerializeDemo2 {public static void main(String[] args) {// SerializeTest2 序列化// 返回结果: writeReplace....try {SerializeTest2 e = new SerializeTest2();FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/SerializeTest2.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();} catch (IOException i) {i.printStackTrace();}// SerializeTest2 反序列化读取 writeReplace() 返回的数据// 返回结果: [test1, test2]try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/java/java反序列化测试/SerializeTest2.ser"))) {System.out.println((ois.readObject()).toString());} catch (FileNotFoundException er) {er.printStackTrace();} catch (IOException er) {er.printStackTrace();} catch (ClassNotFoundException er) {er.printStackTrace();}}}// 运行结果:// 1. SerializeTest2 序列化// 输出: writeReplace....// 并且会在在 /src/main/java/java反序列化测试/ 生成一个 SerializeTest2.ser 文件//// 2. SerializeTest2 反序列化读取 writeReplace() 返回的数据// 输出: [test1, test2]
0x04.2 java.io.ObjectOutputStream 类
0x04.2.1 readObject() - 重点方法
// 第一步创建 DeserializationTest1 类package java反序列化测试;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;public class DeserializationTest1 implements Serializable {/*** 自定义反序列化类对象* 调用: 反序列化时调用** @param ois 反序列化输入流对象* @throws IOException IO异常* @throws ClassNotFoundException 类未找到异常*/private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {System.out.println("readObject...");// 调用ObjectInputStream默认反序列化方法ois.defaultReadObject();}}
// 第二步-序列化 DeserializationTest1 类// 并且反序列化 DeserializationTest1 类package java反序列化测试;import java.io.*;public class DeserializationDemo1 {public static void main(String[] args) {// 序列化try {DeserializationTest1 e = new DeserializationTest1();FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest1.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();} catch (IOException i) {i.printStackTrace();}// 反序列化 DeserializationTest1try {FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest1.ser");ObjectInputStream in = new ObjectInputStream(fileIn);in.readObject();in.close();fileIn.close();} catch (IOException i) {i.printStackTrace();return;} catch (ClassNotFoundException c) {System.out.println("DeserializationTest1 class not found");c.printStackTrace();return;}}}// 运行结果:// 1. DeserializationTest1 序列化// 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest1.ser 文件//// 2. 反序列化 DeserializationTest1 并且自动调用了 readObject()// 输出: readObject...
0x04.2.2 readResolve()
// 第一步创建 DeserializationTest2 类package java反序列化测试;import java.io.Serializable;public class DeserializationTest2 implements Serializable {/*** 读取对象时触发* 有了这个以后 getTestName() 与 getTestValue() 会访问不到** @return 读取后的对象*/protected Object readResolve() {System.out.println("readResolve....");return null;}}
// 第二步-序列化 DeserializationTest2 类// 并且反序列化 DeserializationTest2 类package java反序列化测试;import java.io.*;public class DeserializationDemo2 {public static void main(String[] args) {// 序列化try {DeserializationTest2 e = new DeserializationTest2();FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest2.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();} catch (IOException i) {i.printStackTrace();}// 反序列化 DeserializationTest1try {FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest2.ser");ObjectInputStream in = new ObjectInputStream(fileIn);in.readObject();in.close();fileIn.close();} catch (IOException i) {i.printStackTrace();return;} catch (ClassNotFoundException c) {System.out.println("DeserializationTest2 class not found");c.printStackTrace();return;}}}// 运行结果:// 1. DeserializationTest2 序列化// 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest2.ser 文件//// 2. 反序列化 DeserializationTest2 并且自动调用了 readResolve()// 输出: readResolve....
0x04.2.3 readObjectNoData()
这个方法,说实在,我在看反序列化链子的时候就没见过有怎么使用,并且触发的方式也是奇奇怪怪,所以这个方法很值得我们写个例子,搞清楚到底要怎么样才会触发,然后知道有这么一个东西即可
注意: 它的触发方法是,当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本
注意2: readObjectNoData() 方法是最先执行的
// 第一步创建 DeserializationTest3 类package java反序列化测试;import java.io.Serializable;public class DeserializationTest3 implements Serializable {}
// 第二步创建 ReadObjectNoDataTest 类package java反序列化测试;import java.io.Serializable;public class ReadObjectNoDataTest implements Serializable {/*** 当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本*/private void readObjectNoData() {System.out.println("readObjectNoData...");}}
// 第三步-序列化 DeserializationTest3 类package java反序列化测试;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class SerializeDemo3 {public static void main(String[] args) {// DeserializationTest3 序列化try {DeserializationTest3 e = new DeserializationTest3();FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest3.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();} catch (IOException i) {i.printStackTrace();}}}// 运行结果:// DeserializationTest3 序列化// 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest3.ser 文件
// 第四步-修改 DeserializationTest3 类如下package java反序列化测试;import java.io.Serializable;public class DeserializationTest3 extends ReadObjectNoDataTest implements Serializable {}
// 第五步-反序列化 DeserializationTest3 类package java反序列化测试;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class DeserializationDemo3 {public static void main(String[] args) {// 反序列化 DeserializationTest3try {FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest3.ser");ObjectInputStream in = new ObjectInputStream(fileIn);in.readObject();in.close();fileIn.close();} catch (IOException i) {i.printStackTrace();return;} catch (ClassNotFoundException c) {System.out.println("DeserializationTest3 class not found");c.printStackTrace();return;}}}// 运行结果:// 输出: readObjectNoData...
0x04.2.4 readUnshared()
// 创建 ReadUnsharedDemo 类package java反序列化测试;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class ReadUnsharedDemo {public static void main(String[] args) {String s = "Hello World";try {// 序列化文件FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/test.txt");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeUnshared(s);out.flush();// 反序列化文件ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/java/java反序列化测试/test.txt"));// 读取和打印非共享对象System.out.println("" + ois.readUnshared());} catch (Exception ex) {ex.printStackTrace();}}}// 运行结果:// 输出: Hello World
