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 {
# 喜欢多长自己写
# 注意: 这个类型必须为long
private 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();
}
// 反序列化 DeserializationTest1
try {
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();
}
// 反序列化 DeserializationTest1
try {
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) {
// 反序列化 DeserializationTest3
try {
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