初识Java反序列化

 

前言

研究某产品反序列化EXP时,搜集到的POC只有一段16进制字节序列难以利用,遂有下文对Java序列化和反序列化的学习。

大致内容如下:

  1. 序列化和反序列化示例
  2. 序列化数据组成解构
  3. 反序列化漏洞形成原理

 

序列化和反序列化

序列化:将程序运行时所需要的Java对象转化为字节序列并存储在文件系统中,一般为.ser后缀的文件,ObjectOutputStream.writeObject()方法可以将对象序列化。

反序列化:将存储在文件系统的字节序列转化成对象供程序使用,ObjectInputStream.readObject()方法可以将字节序列转化成对象。

可序列化的类必须继承 java.io.Serializable;序列化机制使对象得以脱离程序之外独立存在。

示列:

Employ.java

import java.io.Serializable;
//该类必须实现java.io.Serializable
public class Employ implements Serializable {
public String name;
public int age;
}

test.java

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class test {
public static void main(String[] args) {
//将e转化为字节序列存储于/tmp/1.ser
Employ e = new Employ();
e.name = “zhangyida”;
e.age = 15;
try {
FileOutputStream fops = new FileOutputStream(“/tmp/1.ser”);
ObjectOutputStream obos = new ObjectOutputStream(fops);
obos.writeObject(e);
obos.close();
System.out.println(“Serialized data is saved in /tmp/1.ser”);
}catch (IOException i){
i.printStackTrace();
}
}
}

desEmploy.java

import java.io.ObjectInputStream;
public class desEmploy {
public static void main(String[] args) {
Employ e = new Employ();
try {
FileInputStream fis = new FileInputStream(“/tmp/1.ser”);
ObjectInputStream obis = new ObjectInputStream(fis);
e = (Employ) obis.readObject();
}catch (IOException i){
i.printStackTrace();
return;
} catch (ClassNotFoundException ex) {
System.out.println(“Employ class not found!”);
ex.printStackTrace();
return;
}
System.out.println(e.name);
System.out.println(e.age);
}
}

运行test.java,生成一个字节序列存储于/tmp目录下。

运行desEmploy.java,将存储在1.ser内的字节序列转化为对象。

 

字节序列数据格式

字节序列格式:

一个java对象序列化后的字节序列由三部分组成(magic、version、contents),其中magic和version是常量,magic表示内容类型,version则是版本号,contents是被序列化对象的属性、状态等内容。java序列化stream的特征aced 0005及aced 0005编码后的字符串。

stream:
magic version contents

字节序列中的contents,可能由一个content组成也可以有多个content。

contents
content
contents content

content由一个或多个的object(对象)、blockdata(数据块)组成。

content
object
blockdata

object(对象),序列化的Stream中常见的对象有newObject、newClassDesc、newString。

object
newObject
newClass
newArray
newString
newEnum
newClassDesc
prevObject
nullReference
exception
TC_RESET

newObject,表示序列化对象是一个普通object对象;标识符为TC_OBJECT;classDesc表示一个ObjectStreamClass对象,其保存着className、序列化ID、类字段等信息;newHandle是其句柄值,类似对象ID;classdata[],保存类实例化对象属性。

newObject:
TC_OBJECT classDesc newHandle classdata[]

newString,表示序列化对象是一个字符串常量对象。

newString:
TC_STRING newHandle

newClassDesc,表示序列化对象是一个ObjectStreamClass对象,TC_CLASSDESC是其标识符;className是其类名;serialVersionID是其序列化版本ID,当对字节序列被反序列化会将此值与本地相应实体类的serialVersionUID比较,一致则反序列化,不一致则抛出异常;classDescINFO保存着类的序列化属性。

classDescINFO:

classDescFlags(0x02 – SC_SERIALIZABLE or 0x03 – SC_WRITE_METHOD | SC_SERIALIZABLE),0x02表示类实现了Serializable接口但并未重写readObject方法,0x03表示类即实现了Serializable接口又重写了readObject方法。当值为0x03时,反序列化该字节序列时会调用重写的readObject方法。

fields:类的属性

classAnnotation:类注解,一般为TC_ENDBLOCKDATA

superClassDesc:被序列化对象的类的父类是否可序列化,可序列化时则写入父类的classDesc,不可序列化时则为TC_NULL。

newClassDesc
TC_CLASSDESC className serialVersionUID newHandle classDescInfo
*classDescInfo
classDescFlags fields classAnnotation superClassDesc

使用SerializationDumper可以查看其序列化之后的相关信息:Employ类的实例对象,Int属性age值为15,String属性name值为zhangyida;Employ类未重写readObject方法。

 

用途及使用场景

用途:

1、将对象的字节序列永久保存在硬盘以便使用。

2、网络传输。

使用场景:

使用场景丰富,简单列举常用场景。

1、服务器启动后,将用户session信息永久保存在硬盘中,当服务器出现问题需要重启时可以直接从硬盘中读取字节序列还原用户seesion。

2、JNDI、RMI远程代码调用

3、xml Xstream、XMLDecoder等(HTTP body:Content-Type:applicatin/xml)、json(Jackson、fastjson)http请求中包含。

4、…

 

反序列化命令执行漏洞原理

被序列化对象的类重写了readObject方法,且重写的readObject方法存在问题可执行java代码造成反序列化命令执行漏洞。

示例:

Employ类重写readObject方法,添加一个命令执行的方法

Employ.java

import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.IOException;
public class Employ implements Serializable {
public String name;
//public int age;
private void test(String name){
System.out.println(name);
}
private void readObject(ObjectInputStream objin) {
try {
objin.readObject();
Runtime.getRuntime().exec(“open /System/Applications/Calculator.app”);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

重新生成字节序列后再反序列化字节序列将执行系统命令弹出计算器,故Java反序列化漏洞就是当前类重写readObject方法时可调用执行系统命令的函数造成远程命令执行的漏洞。因此真实环境中的java反序列化漏洞需要去寻找重写了readObject方法的且可利用的类能通过调用Runtime.getRuntime().exec()或者其他函数来达到执行系统命令目的,这个可利用的类称之为gadget,调用过程称为gadget chain。

(完)