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


























