Java Deserialization - Chapter I
Java Deserialization - Chapter I
S0cke3t序
首先,为什么要写这个系列的文章呢
主要是由于先前在刚接触这块内容时,仅做了相关分析.时间一长一些概念和具体细节渐渐忘却,所以此系列文章主要还是做一个复习和留存,方便日后查阅
其中需要说明的是分析流程可能不会太细节,但关键步骤都会展示.如果你对这方面从未接触过可以结合参考文章进行学习
序列化与反序列化
在 Java 中,序列化就是将一个 Java 对象当前状态以字符串(字节序列)的形式描述出来,这串字符可能被储存/发送到任何需要的位置,而反序列化则是再将它转回原本的 Java 对象。
Java原生类库中提供了两个类用于序列化和反序列化操作,其中
java.io.ObjectOutputStream
用于序列化操作
java.io.ObjectInputStream
用于反序列化操作
其中在java.io.ObjectOutputStream
中主要由writeObject
函数实现序列化操作
而反序列化操作主要由java.io.ObjectInputStream
的readObject
函数实现
需要注意的是,要想序列化一个类则该类必须实现Serializable
或者Externalizable
接口.其中Externalizable
为Serializable
的扩展实现,额外提供了writeExternal
和readExternal
用来序列化和反序列化一些外部的元素
示例
这里通过一个简单示例来具体了解下相关序列化反序列化操作
序列化
首先编写一个实现Serializable
接口的Person
类,并重写readObject
函数实现调用Runtime
执行计算器功能
随后编写一个测试类,实例化一个person
对象,并将其通过ObjectOutputStream
的writeObject
进行序列化保存到person.txt中
执行后person
被成功序列化到person.txt
中,其中不难看出序列化内容的标志头为aced
.这也是我们日后辨别反序列化数据的依据
反序列化
了解了序列化后反序列化就很好理解了,就是将序列化的内容中心还原为java对象
通过ObjectInputStream
的readObject
将上节中person.txt
中的数据进行反序列化
执行后可以看到执行了person
对象中readObject
中的功能
readObject分析
那么问题来了,为什么重写了readObject
就可以执行代码呢,这就需要来具体分析下反序列化具体的实现流程
首先来跟一下ObjectInputStream
的readObject
readObject
实际由readObject0
进行处理
readObject0
会以字节方式读取反序列化数据,其中如果读到115
也就是十六进制的0x73
,也就表示这是一个对象
随后switch
进行匹配,如果是对象则调用readOrdinaryObject
在readOrdinaryObject
主要干了这么几个事
- 读取类描述符
- 判断是否实现
Externalizable
接口,如果是则调用readExternalData
,不是则调用readSerialData
其中类描述符的结构如下
包含了反序列化对象的全限定名称以及serialUid
是否是代理类等等一系列信息
回到readOrdinaryObject
中的判断,很显然我们的类并不是实现的Externalizable
接口,所以会调用readSerialData
readSerialData
首先会获取类布局信息,随后遍历获取类描述,并判断是否重写了readObject
,如果重写了readObject
则调用readSerialData
invokeReadObject
在判断this.readObjectMethod
不会null
后随即利用反射调用反序列化对象的readObject
进入invoke
后就是正常反射调用流程了
最后走到反序列化类的readObject
整个调用堆栈如下
总结
通过上述示例和分析,可以看到如果一个类重写了readObject
并且我们可以控制反序列化的内容再结合相关利用链,那么就可以实现任意命令的执行等操作
关于利用链的分析将会在后续章节中介绍
That’s all