序列化与反序列化
序列化就是将一个对象转化为二进制内容,并且进行持久化存储或者网络传输过程。反序列化从文件或者网络读入二进制序列并且将其转化为Java对象。
一个对象要实现序列化必须实现java.io.Serializable
接口。Serializable无任何方法,只是一个**“标记接口”(Marker Interface)**,或者实现 java.io.Externalizable
接口的方法。同时java对象中支持在类中定义如下函数:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
这两个函数不是java.io.Serializable的接口函数,而是约定的函数,如果一个类实现了这两个函数,那么在序列化和反序列化的时候ObjectInputStream.readObject()和ObjectOutputStream.writeObject()会主动调用这两个函数。这也是反序列化漏洞产生的根本原因
序列化步骤
- 创建一个ObjectOutputStream输出流;
- 调用ObjectOutputStream对象的writeObject输出可序列化对象
反序列化步骤
- 创建一个ObjectInputStream输入流;
- 调用ObjectInputStream对象的readObject()得到序列化的对象。
序列化和反序列化的样例代码如下:
类Payload
实现了Serializable 并且自定义了readObject和writeObject方法
//Payload.java
public class Payload implements Serializable {
private String msg;
public Payload() {
}
public Payload(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String say() {
return "Hello " + msg;
}
/**
* 自定义反序列化逻辑,实现rce
* @param objectInputStream
* @throws IOException
*/
private void readObject(ObjectInputStream objectInputStream) throws IOException{
System.out.println("do serialize....");
Process result = Runtime.getRuntime().exec("ifconfig");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(result.getInputStream()));
do{
System.out.println(bufferedReader.readLine());
}while (bufferedReader.readLine() != null);
}
private void writeObject(ObjectOutputStream objectOutputStream) throws IOException{
System.out.println("do deserialize....");
}
}
// SerializeDemo.java
public class SerializeDemo {
String outName = "123.ser";
byte[] payloadBytes;
public void serialize() throws Exception {
Payload payload = new Payload("world");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(outName));
objectOutputStream.writeObject(payload);
}
public void serializeByte() throws Exception{
Payload payload = new Payload("world2");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(payload);
payloadBytes = byteArrayOutputStream.toByteArray();
objectOutputStream.close();
byteArrayOutputStream.close();
System.out.println(Arrays.toString(payloadBytes));
}
public Object deserialize() throws Exception {
FileInputStream fileInputStream = new FileInputStream(outName);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return objectInputStream.readObject();
}
public Object deserializeBytes() throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payloadBytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
public static void main(String[] args) throws Exception {
SerializeDemo demo = new SerializeDemo();
// System.out.println("serialize finished ...");
// Payload payload = (Payload) demo.deserialize();
// System.out.println("deserialize finished ...");
//
// String result = payload.say();
// System.out.println("result is: " + result);
demo.serializeByte();
Payload payload1 = (Payload) demo.deserializeBytes();
String result1 = payload1.say();
System.out.println("result is: " + result1);
}
识别序列化数据
//序列化后的对象,数据一般以 aced开头
AC ED:STREAM_MAGIC,声明使用了序列化协议,从这里可以判断保存的内容是否为序列化数据。
00 05:STREAM_VERSION,序列化协议版本。
或者使用hexdump查看
> xxd 123.ser
00000000: aced 0005 7372 0011 636f 6d2e 7274 7975 ....sr..com.rtyu
00000010: 792e 5061 796c 6f61 64c9 8f6b 4fbb efb7 y.Payload..kO...
00000020: 7d02 0001 4c00 036d 7367 7400 124c 6a61 }...L..msgt..Lja
00000030: 7661 2f6c 616e 672f 5374 7269 6e67 3b78 va/lang/String;x
00000040: 7074 0005 6865 6c6c 6f pt..hello
反序列化漏洞
Java的反序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
如下:
/**
* 自定义反序列化,实现rce
* @param objectInputStream
* @throws IOException
*/
private void readObject(ObjectInputStream objectInputStream) throws IOException{
System.out.println("do serialize....");
Process result = Runtime.getRuntime().exec("ifconfig");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(result.getInputStream()));
do{
System.out.println(bufferedReader.readLine());
}while (bufferedReader.readLine() != null);
}
漏洞挖掘
代码审计
java是支持自定义readObject与writeObject方法的,只要某个类中按照特定的要求实现了readObject方法,那么在反序列化的时候就会自动调用它。需要格外关注自定义readObject方法的类调用关系或者实现java.io.Externalizable接口的类调用关系。
web黑盒探测
-
通过http请求的Content-Type,具体来说ContentType: application/x-java-serialized-object 是序列化请求的请求头
-
检查请求数据的开头是否是 0xaced,有时候序列化请求不存在正确的content-type,此时需要根据数据来判断是否是序列化请求