Java Deserialization - Chapter I

首先,为什么要写这个系列的文章呢

主要是由于先前在刚接触这块内容时,仅做了相关分析.时间一长一些概念和具体细节渐渐忘却,所以此系列文章主要还是做一个复习和留存,方便日后查阅

其中需要说明的是分析流程可能不会太细节,但关键步骤都会展示.如果你对这方面从未接触过可以结合参考文章进行学习

序列化与反序列化

在 Java 中,序列化就是将一个 Java 对象当前状态以字符串(字节序列)的形式描述出来,这串字符可能被储存/发送到任何需要的位置,而反序列化则是再将它转回原本的 Java 对象。

Java原生类库中提供了两个类用于序列化和反序列化操作,其中

java.io.ObjectOutputStream用于序列化操作

java.io.ObjectInputStream用于反序列化操作

其中在java.io.ObjectOutputStream中主要由writeObject函数实现序列化操作

而反序列化操作主要由java.io.ObjectInputStreamreadObject函数实现

需要注意的是,要想序列化一个类则该类必须实现Serializable或者Externalizable接口.其中ExternalizableSerializable的扩展实现,额外提供了writeExternalreadExternal用来序列化和反序列化一些外部的元素

示例

这里通过一个简单示例来具体了解下相关序列化反序列化操作

序列化

image-20230330154700412

首先编写一个实现Serializable接口的Person类,并重写readObject函数实现调用Runtime执行计算器功能

image-20230330154916109

随后编写一个测试类,实例化一个person对象,并将其通过ObjectOutputStreamwriteObject进行序列化保存到person.txt中

image-20230330155107360

执行后person被成功序列化到person.txt中,其中不难看出序列化内容的标志头为aced.这也是我们日后辨别反序列化数据的依据

反序列化

了解了序列化后反序列化就很好理解了,就是将序列化的内容中心还原为java对象

image-20230330155433003

通过ObjectInputStreamreadObject将上节中person.txt中的数据进行反序列化

image-20230330155626945

执行后可以看到执行了person对象中readObject中的功能

readObject分析

那么问题来了,为什么重写了readObject就可以执行代码呢,这就需要来具体分析下反序列化具体的实现流程

首先来跟一下ObjectInputStreamreadObject

image-20230330160752961

readObject实际由readObject0进行处理

image-20230330161105562

readObject0会以字节方式读取反序列化数据,其中如果读到115也就是十六进制的0x73,也就表示这是一个对象

image-20230330161404696

随后switch进行匹配,如果是对象则调用readOrdinaryObject

image-20230330161554501

readOrdinaryObject主要干了这么几个事

  • 读取类描述符
  • 判断是否实现Externalizable接口,如果是则调用readExternalData,不是则调用readSerialData

其中类描述符的结构如下

image-20230330161948363

包含了反序列化对象的全限定名称以及serialUid是否是代理类等等一系列信息

回到readOrdinaryObject中的判断,很显然我们的类并不是实现的Externalizable接口,所以会调用readSerialData

image-20230330162518855

readSerialData首先会获取类布局信息,随后遍历获取类描述,并判断是否重写了readObject,如果重写了readObject则调用readSerialData

image-20230330162807197

invokeReadObject在判断this.readObjectMethod不会null后随即利用反射调用反序列化对象的readObject

image-20230330163225022

进入invoke后就是正常反射调用流程了

image-20230330163340203

最后走到反序列化类的readObject

image-20230330163440159

整个调用堆栈如下

image-20230330163536077

总结

通过上述示例和分析,可以看到如果一个类重写了readObject并且我们可以控制反序列化的内容再结合相关利用链,那么就可以实现任意命令的执行等操作

关于利用链的分析将会在后续章节中介绍

That’s all

参考

Java 反序列化漏洞(一) - 前置知识 &
URLDNS