在网络上传递信息,通常用到一些格式化数据,例如Json、XML等。但是大多数处理方法中,JSON和XML支持的数据类型就是基本数据类型,整形、浮点型、字符串、布尔等。如果开发者希望在传输数据的时候直接传输一个对象,就需要扩展基础的JSON(XML)语法。
比如Jsackson和Fastjson这类序列化库,在JSON(XML)的基础上进行改造,通过特定的语法来传递对象;亦或者如RMI,直接使用Java等语言内置的序列化方法,将一个对象转换成一串二进制数据进行传输。
不管是Jackson、Fastjson还是编程语言内置的序列化方法,一旦涉及到序列化与反序列化数据,就可能会涉及到安全问题。
一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。即序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。
因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。
那么以上大概可以分成三个主要部分:
Java内置的序列化方法readObject是将十六进制数据流转为对象的方法,更倾向于解决“反序列化时如何还原一个完整对象”。
Java在序列化时一个对象,将会调用这个对象中的 writeObject
方法,参数类型是ObjectOutputStream ,开发者可以将任何内容写入这个stream中;反序列化时,会调用readObject
,开发者也可以从中读取出前面写入的内容,并进行处理。
每个对象都可以重写自己的writeObject
和readObject
方法。
Person person = new Person("杜子腾", 15);
try {
FileOutputStream fileOutputStream = new FileOutputStream("D:\\person.ser");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(person);//序列化
outputStream.close();
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
FileInputStream fileInputStream = new FileInputStream("D:\\person.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();//反序列化
objectInputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
工具:SerializationDumper可以查看序列化的数据、ysoserial可以生成反序列化利用数据。
利用链也叫“gadget chains”,我们通常称为gadget。它连接的是从触发位置开始到执行命令的位置结束,在PHP里面可能是_desctruct
到eval
。gadget就是一种生成POC的方法。