最近在看Java反序列化漏洞方面的文章,感谢各位大佬的分享。由此做一个整理,并加入自己的理解。

序列化与反序列化

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。
简单来说序列化是用于将对象转换成二进制串存储,而反序列化即为将二进制串转换成对象。
未命名文件-3

为什么要序列化

在运行Java中,我们会通过各种途径创建对象,这些对象事实上都是位于JVM的堆内存中,伴随着其运行而存在。一旦当JVM停止运行,这些对象的状态也就随之消失。在实际中,我们需要将这些对象持久下来并且在需要时候读出来,或者为了节省内存,这时候就需要使用序列化。
对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

使用场景

  • http参数,cookie,sesion,存储方式可能是base64(rO0), 压缩后的base64(H4sl),MII等
  • Servlets HTTP,Sockets,Session管理器 包含的协议就包括 JMX,RMI,JMS,JNDI等(\xac\xed)
  • xml Xstream,XMLDecoder等(HTTP Body:Content- Type:application/xml)
  • json(Jackson,fastjson)http请求中包含

代码实现

相关接口及类

Java为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类:

java.io.Serializable

java.io.Externalizable

ObjectOutput

ObjectInput

ObjectOutputStream

ObjectInputStream 

使用时可序列化的对象需要实现 java.io.Serializable 接口或者 java.io.Externalizable 接口。
以实现 Serializable 接口为例,Serializable 仅是一个标记接口,并不包含任何需要实现的具体方法。实现该接口只是为了声明该Java类的对象是可以被序列化的。实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。ObjectOutputStream 的 writeObject 方法可以把一个Java对象写入到流中,ObjectInputStream 的 readObject 方法可以从流中读取一个 Java 对象。在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。Java 会自动帮你遍历对象图并逐个序列化。除了对象之外,Java 中的基本类型和数组也是可以通过 ObjectOutputStream 和 ObjectInputStream 来序列化的。

示例代码

序列化

public class Ser implements Serializable{
    private static final long serialVersionUID = 1L;
    public int num=911;
    public static void main(String[] args) {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
            Ser ser=new Ser();
            oos.writeObject(ser);//序列化关键函数
            oos.flush();  //缓冲流
            oos.close(); //关闭流
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

可以看到,在序列化后当前目录下生成了一串二进制表示的字节数组文件object.obj
接下来我们执行反序列化读出其对象中的参数
反序列化

public class DeSer {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.obj"));
        Ser s = (Ser) ois.readObject();
        System.out.println(s.num);
        ois.close();
    }
}

屏幕快照 2018-07-10 上午11.07.56

存储格式

序列化的文件二进制字节数据如下图
屏幕快照 2018-07-10 上午11.19.01
1.序列化文件头

AC ED :STREAM_MAGIC声明使用了序列化协议
00 05 :STREAM_VERSION序列化协议版本
73 :TC_OBJECT声明这是一个新的对象

2.序列化类的描述 在这里是Ser类

72 :TC_CLASSDESC声明这里开始一个新的class
00 11 :class名字的长度是17个字节
63 6E 2E 72 75 69 30 2E 74 65 73 74 31 2E 53 65 72 :Ser的完整类名
00 00 00 00 00 00 00 01 :serialVersionUID,序列化ID,如果没有指定,则会由算法随机生成一个8字节的ID
02 :标记号,声明该类支持序列化
00 01:该类所包含的域的个数为1

3.对象中各个属性的描述

49 :域类型,49代表I,也就是int类型
00 03 :域名字的长度为3
6E 75 6D :num属性的名称

4.对象的父类信息描述

这里没有父类,如果有,则数据格式与第二部分一样

5.对象属性的实际值

如果属性是一个对象,那么这里还将序列化这个对象,规则和第2部分一样
00 00 03 8F :911的数值

当然我们也可以使用工具SerializationDumper来查看其结构
屏幕快照 2018-07-10 上午11.17.02

注意

  • 当父类实现了Serializable接口的时候,所有的子类都能序列化
  • 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,但是数据会丢失)
  • 如果序列化的属性是对象,对象必须也能序列化,否则会报错
  • 反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错
  • 在反序列化的时候serialVersionUID被修改的话,会反序列化失败
  • 在存Java环境下使用Java的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml,json,或则protobuf

参考资料

https://www.cnblogs.com/senlinyang/p/8204752.html
https://www.ibm.com/developerworks/cn/java/j-lo-serial/
http://www.hollischuang.com/archives/1150
http://xxlegend.com/2018/06/20/先知议题%20Java反序列化实战%20解读/