Java反序列化漏洞简单理解

反序列化原理


关于反序列化的原理不在多说,和php类似,序列化的数据是方便存储的,而存储的状态信息想要再次调用就需要反序列化

Java反序列化的API实现


实现方法

  • Java.io.ObjectOutputStream

  • java.io.ObjectInputStream

序列化:ObjectOutputStream类 –> writeObject()

注:该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中,输出的文件为二进制

反序列化: ObjectInputStream类 –> readObject()

注:该方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回

对象可序列化的要求

  • 实现SerializableExternalizable接口的类的对象才能被序列化

  • Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式

序列化实例

下面给出一个序列化的实例,首先是实现Serializable接口待序列化对象

1
2
3
4
5
6
7
8
public class Employee implements java.io.Serializable{//定义实现了Serializable接口的Employee类
public String name; //定义name变量
public String identify; //定义身份变量
public void mailCheck()
{
System.out.println("This is the "+this.identify+" of our company");
} //输出函数
}

序列化类代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;

public class sdemo { //序列化类
public static void main(String [] args) //主函数
{
Employee e = new Employee(); //实例化Employee类
e.name = "admin";
e.identify = "admin"; //实例化类的属性
try //抓取异常
{
FileOutputStream fileOut = new FileOutputStream("E:\\test\\test.db"); // 打开一个文件输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);// 建立对象输入流
out.writeObject(e);//输出反序列化对象
out.close();//关闭对象流
fileOut.close();//关闭文件流
System.out.printf("数据保存在 E:\\test\\test.db文件中");
}catch(IOException i)
{
i.printStackTrace();
}
}
}

执行函数,如下

img

文件内容如下,也不是很好看懂,毕竟是二进制文件,直接打开会乱码

img

而文件的二进制形态是什么样呢?

java序列化的数据库一般都是aced0005开头,当然严格来说应该是aced开头,0005有时候会不太一样,我昨天的序列化数据就是2005,查询某些资料说是跟什么版本有关

img

下面我们对 test.db 文件进行反序列化,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;

public class UnSDemo {
public static void main(String [] args)
{
Employee e = null;//和php类似,我们需要有个对象来接受反序列化的数据
try
{
FileInputStream fileIn = new FileInputStream("E:\\test\\test.db");// 打开一个文件输入流
ObjectInputStream in = new ObjectInputStream(fileIn);// 建立对象输入流
e = (Employee) in.readObject();// 通过readobject方法读取对象
in.close();//关闭对象流
fileIn.close();//关闭文件流
}catch(IOException i) {
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
System.out.println("未发现test.db文件");
c.printStackTrace();
return;
}
System.out.println("反序列化成功...");
System.out.println("Name: " + e.name);
System.out.println("identify: "+e.identify);
}
}

执行结果如下

img

反序列化漏洞

php饭学列化漏洞类似,要想产生漏洞,必要的条件就是参数可控啊

而我们在利用反序列化漏洞的时候肯定是想getshell或者命令执行啊,但是默认的readobject方法是无法帮我们实现这些要求的,下面说一下漏洞的两大成因

开发失误

开发人员对反序列化完全没有进行安全审查,在被序列化的对象类中重写了readobject方法,那么在反序列的过程中,会使用被反序列化类的readObejct方法

如下代码会成功弹出计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.test;
import java.io.*;

public class test {
public static void main(String args[]) throws Exception{

UnsafeClass Unsafe = new UnsafeClass();
Unsafe.name = "弹出计算器";

FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
//writeObject()方法将Unsafe对象写入object文件
os.writeObject(Unsafe);
os.close();
//从文件中反序列化obj对象
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
//恢复对象
UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
}
class UnsafeClass implements Serializable{
public String name;
//重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
Runtime.getRuntime().exec("calc.exe");
}
}

如上图我们在UnsafeClass类中定义了name属性,并且重写了readobject方法,在原有的基础上添加了执行命令的代码,最中弹出计算器

img

而在实际环境中,有些常识的开发者都不会直接将命令写在readObject中,因此此处就需要通过反射链来进行任意代码执行了

反射链

end

下章分析Commons Collections