1. 序列化和反序列化

1.1 为啥需要序列化以及反序列化

其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。

因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)

如果我们要把一栋房子从一个地方运输到另一个地方去,序列化就是我把房子拆成一个个的砖块放到车子里,然后留下一张房子原来结构的图纸,反序列化就是我们把房子运输到了目的地以后,根据图纸把一块块砖头还原成房子原来面目的过程1

1.2 序列化的方式

序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有1

JDK(不支持跨语言)、JSON(常见)XMLHessianKryo(不支持跨语言)、ThriftProtostuffFST(不支持跨语言)

说了以上这么多,大致理解了序列化和反序列化的作用,并且在实际场景中,反序列化会出现哪些问题?,以及如何挖掘以及防御呢?

2. JAVA 序列化与反序列化

首先上代码,现如今的程序语言都是面向对象的程序设计,那么我们先定义一个最简单的类,部分code来源,出于对参考学习的文章作者的尊重,将会注明2

对于需要反序列化的对象,需要继承Serializable类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

/**User.java**/
import java.io.Serializable;
/**定义一个User,其中包含两个方法,一个私有变量,并且User类继承Serializable类。**/
public class User implements Serializable{
private String name;
/**设置方法**/
public void setName(String name) {
this.name=name;
}
/**间接获取类内私有变量**/
public String getName() {
return name;
}
}
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
26
27
28
29
30
31
32
33

/**Main.java**/
import java.io.*;

public class Main {
public static void main(String[] args) throws Exception {
/**实例化对象**/
User user=new User();
/**给里面的静态变量赋值**/
user.setName("leshier");
/** 序列化该对象,返回值为字节流类型**/
byte[] serializeData=serialize(user);
/**将字节流保存到本地。**/
FileOutputStream fout = new FileOutputStream("user.bin");
fout.write(serializeData);
fout.close();
/**重新读取user.bin 文件,并且反序列化**/
User user2=(User) unserialize(serializeData);
/**输出反序列化对象中的变量状态**/
System.out.println(user2.getName());
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

结果如下:

image-20201218142455162

image-20201218143319582

好了,到了这一步,我们试想,将对象改成恶意对象的话呢?

image-20201218145157524

执行命令成功,证明这种方法是可行的,但是这种场景太完美了,现实场景中不会存在。

TODO