readObject
方法,以完整的重新生成这个类的对象 。
readUnshared从 ObjectInputStream 读取一个非共享对象 。此方法与 readObject
类似,不同点在于readUnshared
不允许后续的 readObject
和 readUnshared
调用引用这次调用反序列化得到的对象 。
readObject0readObject
和 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();}}
![1 Java安全之反序列化](http://img.zhejianglong.com/231019/1S5194063-0.png)
文章插图
那为什么我们重写了readObject就会执行呢?来看一下
java.io.ObjectInputStream#readObject()
方法的具体实现代码 。readObject
方法实际调用 readObject0
方法反序列化字符串![1 Java安全之反序列化](http://img.zhejianglong.com/231019/1S5195591-1.png)
文章插图
readObject0
方法以字节的方式去读 , 如果读到 0x73
,则代表这是一个对象的序列化数据,将会调用 readOrdinaryObject
方法进行处理![1 Java安全之反序列化](http://img.zhejianglong.com/231019/1S5194C1-2.png)
文章插图
readOrdinaryObject
方法会调用 readClassDesc
方法读取类描述符,并根据其中的内容判断类是否实现了 Externalizable
接口 , 如果是,则调用 readExternalData
方法去执行反序列化类中的 readExternal
, 如果不是,则调用 readSerialData
方法去执行类中的 readObject
方法![1 Java安全之反序列化](http://img.zhejianglong.com/231019/1S51a196-3.png)
文章插图
在
readSerialData
方法中,首先通过类描述符获得了序列化对象的数据布局 。通过布局的 hasReadObjectMethod
方法判断对象是否有重写 readObject
方法,如果有,则使用 invokeReadObject
方法调用对象中的 readObject
![1 Java安全之反序列化](http://img.zhejianglong.com/231019/1S5191F9-4.png)
文章插图
我们就了解了反序列化漏洞的触发原因 。与反序列漏洞的触发方式相同,在序列化时 , 如果一个类重写了
writeObject
方法,并且其中产生恶意调用 , 则将会导致漏洞,当然在实际环境中 , 序列化的数据来自不可信源的情况比较少见 。那接下来该如何利用呢?我们需要找到那些类重写了
readObject
方法,并且找到相关的调用链,能够触发漏洞 。推荐阅读
- 关于.Net和Java的看法-一个小实习生经历
- Java安全之Resin2内存马
- SimpleDateFormat线程安全问题排查
- 请问苹果手机怎么样(苹果手机的安全性怎么样)
- Day04:Java数据类型
- 四十 Java开发学习----MyBatisPlus入门案例与简介
- JavaScript之数组高阶API—reduce
- 源码级深度理解 Java SPI
- 死磕Java面试系列:深拷贝与浅拷贝的实现原理
- JAVA开发搞了一年多的大数据,究竟干了点啥