序列化与反序列化

概述

Java序列化是指把Java对象转换为字节序列的过程;这串字符可能被储存/发送到任何需要的位置,在适当的时候,再将它转回原本的 Java 对象,而Java反序列化是指把字节序列恢复为Java对象的过程。

为什么需要序列化与反序列化

当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象

Java 提供了两个类 java.io.ObjectOutputStream 和 java.io.ObjectInputStream 来实现序列化和反序列化的功能,其中 ObjectInputStream 用于恢复那些已经被序列化的对象,ObjectOutputStream 将 Java 对象的原始数据类型和图形写入 OutputStream。

在 Java 的类中,必须要实现 java.io.Serializable 或 java.io.Externalizable 接口才可以使用,而实际上 Externalizable 也是实现了 Serializable 接口

ObjectOutputStream

ObjectOutputStream 继承的父类或实现的接口如下:

  • 父类 OutputStream:所有字节输出流的顶级父类,用来接收输出的字节并发送到某些接收器(sink)。
  • 接口 ObjectOutput:ObjectOutput 扩展了 DataOutput 接口,DataOutput 接口提供了将数据从任何 Java 基本类型转换为字节序列并写入二进制流的功能,ObjectOutput 在 DataOutput 接口基础上提供了 writeObject 方法,也就是类(Object)的写入。
  • 接口 ObjectStreamConstants:定义了一些在对象序列化时写入的常量。常见的一些的比如 STREAM_MAGIC、STREAM_VERSION 等。

通过这个类的父类及父接口,我们大概可以理解这个类提供的功能:能将 Java 中的类、数组、基本数据类型等对象转换为可输出的字节,也就是反序列化。接下来看一下这个类中几个关键方法

writeObject

这是 ObjectOutputStream 对象的核心方法之一,用来将一个对象写入输出流中,任何对象,包括字符串和数组,都是用 writeObject 写入到流中的。

之前说过,序列化的过程,就是将一个对象当前的状态描述为字节序列的过程,也就是 Object -> OutputStream 的过程,这个过程由 writeObject 实现。writeObject 方法负责为指定的类编写其对象的状态,以便在后面可以使用与之对应 readObject 方法来恢复它

writeUnshared

用于将非共享对象写入 ObjectOutputStream,并将给定的对象作为刷新对象写入流中。

使用 writeUnshared 方法会使用 BlockDataOutputStream 的新实例进行序列化操作,不会使用原来 OutputStream 的引用对象。

writeObject0

writeObject 和 writeUnshared 实际上调用 writeObject0 方法,也就是说 writeObject0是上面两个方法的基础实现。具体的实现流程将会在后面再进行详细研究。

writeObjectOverride

如果 ObjectOutputStream 中的 enableOverride 属性为 true,writeObject 方法将会调用 writeObjectOverride,这个方法是由 ObjectOutputStream 的子类实现的。

在由完全重新实现 ObjectOutputStream 的子类完成序列化功能时,将会调用实现类的 writeObjectOverride 方法进行处理。

ObjectInputStream

ObjectInputStream 继承的父类或实现的接口如下:

  • 父类 InputStream:所有字节输入流的顶级父类。
  • 接口 ObjectInput:ObjectInput 扩展了 DataInput 接口,DataInput 接口提供了从二进制流读取字节并将其重新转换为 Java 基础类型的功能,ObjectInput 额外提供了 readObject 方法用来读取类。
  • 接口 ObjectStreamConstants:同上。

ObjectInputStream 实现了反序列化功能,看一下其中的关键方法。

readObject

从 ObjectInputStream 读取一个对象,将会读取对象的类、类的签名、类的非 transient 和非 static 字段的值,以及其所有父类类型。

我们可以使用 writeObject 和 readObject 方法为一个类重写默认的反序列化执行方,所以其中 readObject 方法会 “传递性” 的执行,也就是说,在反序列化过程中,会调用反序列化类的 readObject 方法,以完整的重新生成这个类的对象。

readUnshared

从 ObjectInputStream 读取一个非共享对象。 此方法与 readObject 类似,不同点在于readUnshared 不允许后续的 readObject 和 readUnshared 调用引用这次调用反序列化得到的对象。

readObject0

readObject 和 readUnshared 实际上调用 readObject0 方法,readObject0是上面两个方法的基础实现。

readObjectOverride

由 ObjectInputStream 子类调用,与 writeObjectOverride 一致。

通过上面对 ObjectOutputStream 和 ObjectInputStream 的了解,两个类的实现几乎是一种对称的、双生的方式进行

反序列化漏洞

一个类想要实现序列化和反序列化,必须要实现 java.io.Serializable 或 java.io.Externalizable 接口。

Serializable 接口是一个标记接口,标记了这个类可以被序列化和反序列化,而 Externalizable 接口在 Serializable 接口基础上,又提供了 writeExternal 和 readExternal 方法,用来序列化和反序列化一些外部元素。

其中,如果被序列化的类重写了 writeObject 和 readObject 方法,Java 将会委托使用这两个方法来进行序列化和反序列化的操作。

正是因为这个特性,导致反序列化漏洞的出现:在反序列化一个类时,如果其重写了 readObject 方法,程序将会调用它,如果这个方法中存在一些恶意的调用,则会对应用程序造成危害。

在这里我们利用写一个简单的测试程序,如下代码创建了 Person 类,实现了 Serializable 接口,并重写了 readObject 方法,在方法中使用 Runtime 执行命令弹出计算器

public class Person implements Serializable {
private String name;
private int age; public Person(String name, int age) {
this.name = name;
this.age = age;
} private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
Runtime.getRuntime().exec("calc.exe");
} }

然后我们将这个类序列化并写在文件中,随后对其进行反序列化,就触发了命令执行

public class SerializableTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("gk0d", 24); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(person);
oos.close(); FileInputStream fis = new FileInputStream("test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
}

那为什么我们重写了readObject就会执行呢?来看一下 java.io.ObjectInputStream#readObject() 方法的具体实现代码。

readObject 方法实际调用 readObject0 方法反序列化字符串

readObject0 方法以字节的方式去读,如果读到 0x73,则代表这是一个对象的序列化数据,将会调用 readOrdinaryObject 方法进行处理

readOrdinaryObject 方法会调用 readClassDesc 方法读取类描述符,并根据其中的内容判断类是否实现了 Externalizable 接口,如果是,则调用 readExternalData 方法去执行反序列化类中的 readExternal,如果不是,则调用 readSerialData 方法去执行类中的 readObject 方法

在 readSerialData 方法中,首先通过类描述符获得了序列化对象的数据布局。通过布局的 hasReadObjectMethod 方法判断对象是否有重写 readObject 方法,如果有,则使用 invokeReadObject 方法调用对象中的 readObject

我们就了解了反序列化漏洞的触发原因。与反序列漏洞的触发方式相同,在序列化时,如果一个类重写了 writeObject 方法,并且其中产生恶意调用,则将会导致漏洞,当然在实际环境中,序列化的数据来自不可信源的情况比较少见。

那接下来该如何利用呢?我们需要找到那些类重写了 readObject 方法,并且找到相关的调用链,能够触发漏洞。

Java安全之反序列化(1)的更多相关文章

  1. Java序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  2. [转] Java序列化与反序列化

    原文地址:http://blog.csdn.net/wangloveall/article/details/7992448 Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java ...

  3. Java序列化与反序列化(Serializable)

    Java序列化与反序列化(Serializable) 特别注意: 1.要序列化的类必须实现Serializable借口 2.在反序列化(读取对象)的时候必须额外捕获EOFException 3.序列化 ...

  4. Java基础(五)-Java序列化与反序列化

    .output_wrapper pre code { font-family: Consolas, Inconsolata, Courier, monospace; display: block !i ...

  5. JAVA序列化和反序列化XML

    package com.lss.utils; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.Bu ...

  6. Java序列化与反序列化(实践)

    Java序列化与反序列化(实践) 基本概念:序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数据. 昨天在一本书上 ...

  7. java序列化与反序列化(转)

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  8. ref:Java安全之反序列化漏洞分析(简单-朴实)

    ref:https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247484200&idx=1&sn=8f3201f44e ...

  9. Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨.  1.Java序列化与反序列化  Java序列化是指把Java对象转换为字节 ...

  10. (记录)Jedis存放对象和读取对象--Java序列化与反序列化

    一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...

随机推荐

  1. Linux 禁止root远程登录解决办法

    linux中root用户是超级管理员,可以针对root用户暴力破解密码,这样很不安全,工作中我们一般禁止root用户直接远程登陆,开设一个或多个普通用户,只允许登陆普通用户,如果有需要用root用户, ...

  2. OpenDrop 这样的应用程序以及与当今流行的替代品的比较

    由安全移动网络实验室 OpenDrop 创建的用 Python 编写的开放 Apple AirDrop 实现是一个命令行工具,允许直接通过 Wi-Fi 在设备之间共享文件.它的独特之处在于它与 App ...

  3. 数据结构与算法【Java】05---排序算法总结

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  4. mysql8.0及以上修改Root密码

    ALTER user 'root'@'localhost' IDENTIFIED BY 'Cliu123#' //1.不需要flush privileges来刷新权限. //2.密码要包含大写字母,小 ...

  5. 自定义View5 -塔防小游戏:第二篇防御塔随意放置

    第一篇:一个防御塔+多个野怪(简易版) 第二篇:防御塔随意放置 自定义View,处理事件分发,up,move,down. 第三篇:防御塔随意放置+多组野怪 第四篇:多波野怪 第五篇:杀死野怪获得金币 ...

  6. Java 函数式编程

    由 JS 转 Java,写惯了 React,习惯了函数式,因此转 Java 时也是先学函数式. 语法糖「Syntactic Sugar」 起初,Java 的函数式看起来是匿名类的一个语法糖. Stre ...

  7. Java 快速开发几 MB 独立 EXE,写图形界面很方便

    Java 写的桌面软件带上运行时只有 6 MB,而且还是独立 EXE 文 件,是不是难以置信? 想一想 Electron 没写多少功能就可能超过百 MB 的体积,Java 写的桌面软件算不算得上小.轻 ...

  8. 云原生之旅 - 2)Docker 容器化你的应用

    前言 上文中我们用Golang写了一个HTTP server,本篇文章我们讲述如何容器化这个应用,为后续部署到kubernetes 做准备. 关键词:Docker, Containerization, ...

  9. SQL Server如何精准匹配字符中的字符,绝对匹配。

    举例: 我现在是需要查询这字段里包含1的数据 我如果直接charindex,那么11,12也会被包含. 解决(1): SELECT * FROM ( SELECT '1,2,12,111' AS st ...

  10. 【前端必会】前端开发利器VSCode

    介绍 工欲善其事必先利其器,开发工具方面选择一个自己用的顺手的,这里就用VSCode 安装参考 https://www.runoob.com/w3cnote/vscode-tutorial.html ...