这里的Java对象可以理解为我们的数据结构。
(1) 序列化:
把对象转化为可传输的字节序列过程称为序列化。
(2) 反序列化:
把字节序列还原为对象的过程称为反序列化。
(3) 序列化的含义:
我们都知道在内存中的数据结构都有一个特点,即存储的时间不持久。比如此时此刻在我们的内存中有一段Java的程序,然后里面有一个方法,这个方法里面有一个对象,只要这段程序运行结束了或者这个JVM停止了,或者一些服务停止了,比如tomcat,又或者是这台服务器重启了,那么我们内存中的这些数据结构就会全部丢失。
因此我们就会有一种需求,即让这段程序即使运行结束了,我们仍然能够在别的地方继续使用这段程序,那么我们就要将这段程序保存于磁盘中或者通过网络去传输到其他地方才能满足我们的需求。
所以我们把存储于内存中的数据结构保存到磁盘或者通过网络传输的过程我们称为序列化。
(4) 序列化的用途(使用场景):
本来http协议它是无状态的,那么也就是说我们每一个http请求都是独立的。那我们登录的用户刷新了一下网页肯定不需要再次输入用户名密码,让服务端给我继续保持会话。
这时我们客户端所用到的技术叫cookie,即就是在客户端保存一个我们已经登录的身份标志,同时在服务端也要把这个身份标志保存起来,然后把从客户端发送过来的身份标志与服务端保存的这个身份标志拿出来两者进行对比(校验),那么我们把从服务端保存的身份标志拿出来进行对比(校验)的技术叫session。
如果我们用内存保存我们用户登录状态的信息,那么就会出现前面相同的问题,即程序运行结束或者JVM停止,又或者服务器重启导致内存中保存的session数据全部丢失,那么这样就达不到我们所想要的需求。因此我们要进行持久化内存数据。
如果我们采用了持久化内存数据,将用户登录状态的信息保存于磁盘中,那么即使服务器重启了,它会自动的从磁盘中将我们用户登录状态的信息读取出来恢复到我们的内存中,然后把session中的数据全部都加载出来,这样就不会造成session数据丢失,我们的用户登录的状态依然不会退出登录。
假设有两个业务系统,我想要发送一个Java对象给你,我怎么去发送呢?
这时肯定要把发送的这个Java对象转化成二进制字节流用网络去传输。
(5) 进行序列化之后形成的是字符串呢还是二进制流?
比如php语言我们发现进行序列化之后形成的是我们可以阅读的字符串,那么在Java语言中可以形成可阅读的字符串吗?
在早期用Java语言做开发的时候采用了一种json类型的数据格式,用来在网络中相互调用接口或者传输数据。
在10年的时候流行以下几种类型的数据格式(指进行序列化之后形成的字符串格式):
比如我们常说的webservice,它其实本质上就是http和XML结合而已。
还有比如早期开发的人民银行二代支付系统就是采用以XML格式的数据进行通信的。
也就是说如果我们XML数据的格式不对,例如少了一个标签或者说不在它允许的范围之内。比如一个下拉框我只允许三种值,你超出了这个值,那么这个dtd的文件就会校验出这个错误,因此我们就可以预先知道这个错误而避免这个错误。
所以现在最流行的一种数据交换的格式仍然是JSON(指进行序列化之后形成的字符串格式)。
这里就要用到一个现成的json库,即阿里巴巴的fastjson(pom.xml)。
这里我首先定义好了一个Person对象,Person对象里面有两个属性,分别是字符型的name和整型的age。
然后我new出了一个Person,将name属性赋值为fast,将age属性赋值为999。接着我们用fastjson序列化的时候直接用toJSON的方法就可以了,然后得到一个json格式的序列化对象。
最后我将序列化之后的结果把它反序列化为一个Java的对象,然后我再调用这个对象的方法去拿到它的属性值。
执行代码得到json格式的序列化内容:
这里我们如果要使用JDK去实现序列化和反序列化必须要用到Serializable的接口。
这里我继续我new出了一个Person,将name属性赋值为wuya,将age属性赋值为666,并且我指定了一个序列化之后那个二进制文件保存的位置。
要在JDK中实现Java的序列化和反序列化要用到四个方法,即ObjectOutputStream与write0bject和ObjectInputStream与readobject。
我们打开序列化的二进制文件,这里必须是有一个规范的,所有的Java经过JDK进行序列化它都是这个规范,即AC ED ...分别代表什么,这里不做详细介绍,如果感兴趣的话可以去Java的官网查询。
通过前面的学习我们知道如果用JDK反序列化它需要去实现Serializable的接口,然后实际上去调用readobject这个方法进行反序列化。
因为这个readobject的方法(类)它是可以自定义的,它不是固定的,比如我现在去自定义一个方法,这个方法能够重写readobject方法并且里面执行了一些命令,即打开计算器。如果我在使用readobject方法反序列化的时候用到的是我自定义的这个方法,那么它就不会再调用它原来io里面的readobject方法了,而是调用了我自己定义的方法。如果我自定义的这个方法里面的一些命令可以被执行,再加上里面的参数可以被控制,那么就引发了一个反序列漏洞。
成功执行了我们自己定义的readobject方法。
如果有一个接口它允许接收序列化之后的二进制文件(字符串json格式)的话,那么我们是不是可以构建一个恶意方法让它去反序列化。
其实并没有这么简单,你自己构建的这个类不一定在别人的Java项目里面,你得先去看一下你要攻击的项目里面有没有这样的一个类你才可以去进行反序列化它,就像前面得Person类。如果没有这个类你就要去引用这个依赖或者构建它这个项目里面已经存在的类。
第二:里面的参数可以被控制,即我让它去执行什么样的命令它就去执行什么样的命令。
还真有,比如AnnotationlnvocationHandler类和BadAttributeValueExpException类。
由AnnotationInvocationHandler类引起的反序列化漏洞:
Apache Commons Collections反序列化漏洞
原文始发于微信公众号(零日安全实验室):Web篇 | 一文掌握Java反序列化漏洞,干货!(上)
评论