java反序列化基础
- 序列化:将对象转化为字节流以便传递
- 反序列化:序列化逆过程,字节流转化为对象
- 漏洞成因:反序列化类中有执行系统命令的代码,而没有对序列化字节流进行检测
- 工具:ysoserial
序列化与反序列化
实体类实现
java.io.Serializable
接口serialVersionId
用于标识序列化类的版本,防止类名相同导致反序列化错误静态属性(static)、transient修饰的属性不参与序列化过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 实体类
public class User implements Serializable {
public static final long serialVersionUID = 1L;
public String name;
public int id;
public String sex;
public User() {
this("alice",123, "female");
}
public User(String name, int id, String sex) {
this.name = name;
this.id = id;
this.sex = sex;
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
}
}- 序列化通过
ObjectOutputStream.writeObject(Object)
实现 - 反序列化通过
ObjectInputStream.readObject(Object)
实现 - java在序列化和反序列化时,若实体类实现了
Serializable
接口,则会调用其中的writeObject
和readObject
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// Serialize是自己编写的接口
public interface Serialize {
void output(Object obj,String file) throws IOException;
void input(String file) throws IOException, ClassNotFoundException;
}
// SerializeImpl实现了自己写的接口
public class SerializeImpl implements Serialize {
public void output(Object obj, String file) throws IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
outputStream.writeObject(obj);
outputStream.close();
}
public void input(String file) throws IOException, ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
Object outUser = inputStream.readObject();
inputStream.close();
System.out.println(outUser);
}
}- 序列化通过
- 序列化文件
serialVersionUID详解
Java序列化机制通过判断类的serialVersionId来验证版本是否一致
serialVersionUID可以显式声明,一般默认声明为1L;若不显式声明,则java序列化机制会根据Class自动生成一个serialVersionId
显式声明:
public static final long serialVersionUID= 1L;
声明与否的几种情况
- 当不做显式声明时,会自动生成一个serialVersionUID
情况一:不做显式声明的实体类序列化后修改实体类再反序列化
结果:抛出异常Exception in thread "main" java.io.InvalidClassException: test.User; local class incompatible: stream classdesc serialVersionUID = 592275709804988011, local class serialVersionUID = -7989443708455138686
情况二:有显式声明的实体类序列化后修改实体类(减少属性)再反序列化
- 使用两个项目表示A端(序列化端)B端(反序列化端)
- A端的实体类User比B端多一个属性,其他均一致
结果:B端成功反序列化,执行了B端自己的toString()
方法
- 左边为A端,右边为B端
- 控制台输出为各自序列化同一个字节流文件的结果,可以看到B端在缺少一个属性的情况下仍然序列化成功
情况三:有显示声明的实体类序列化后修改实体类(增加属性)再反序列化
- 和上面反过来,B端序列化,A端反序列化,A端比B端多一个属性
结果:A端成功反序列化,执行了自己的toString()
,多的属性被赋了null初始值
- 左A右B,B序列化,A反序列化
情况四:任意一方没有显式声明
结果:抛出异常,因为没有声明的一方会自动生成一个serialVersionUID
定制序列化数据
- 当我们不需要把整个类的所有属性都序列化时可以自定义需要序列化的属性
1 | private void writeObject(ObjectOutputStream s) throws IOException { |
- 反序列化时会按照顺序读出属性的值,如果没有按顺序赋值也可以反序列化成功,但是属性的值会错乱
- 可以看到下面反序列化操作赋值顺序错乱,但是反序列化仍然会成功,只是赋值错乱而已
transient关键字
- 被transient修饰的属性不会被序列化
- 可以看到被transient修饰的aTransient反序列化后变为初始值null
- transient修饰name,单个写入也一样,还是无法被序列化