前言

序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

Java序列化与反序列原理

在java中,序列化的处理:

1.ObjectOutputStream类中的writeObject方法用来处理需要序列化的对象。

2.ObjectInputStream类中的readObject方法用来处理反序列化。

3.被序列化的类要实现java.io.Serializable接口。

我们先用一个demo来看看java是如何处理序列化与反序列化的

1
2
3
4
5
6
7
8
9
10
11
import  java.io.Serializable;

public class User implements Serializable {
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;

public class Demo {
public static void main(String[] args) throws Exception{
User user = new User();
//serialize
user.setName("monitor");
FileOutputStream fileOutputStream = new FileOutputStream("test.ser");
ObjectOutputStream obos = new ObjectOutputStream(fileOutputStream);
obos.writeObject(user);
obos.close();
//deserialize
FileInputStream fileInputStream = new FileInputStream("test.ser");
ObjectInputStream obis = new ObjectInputStream(fileInputStream);
obis.readObject();
}
}

可以看到,java在对象进行序列化之前,会创建一个文件流,序列化后的数据就会保存在这个文件中。当然,如果不想保存到文件,也可以使用ByteArrayOutputStream生成字节流 。接着会生成一个ObjectOutputStream的对象,然后调用writeObject方法进行序列化,生成的数据保存到test.ser文件中,反序列化流程差不多,就不解释了。

熟悉PHP的都知道,PHP在序列化之后得到的一串可读字符串,甚至在特别情况下,可以伪造序列化字符串进行攻击,那么java下面来看看java生成的序列化数据内部存在什么,这里用到SerializationDumper工具。

java-learn1

这里主要说几个点,想要详细了解自行百度。STREAM_MAGIC:0xaced魔术头,是java序列化数据的特征,;STREAM_VERSION:0x0005序列化使用的JDK版本号,在contents中有一个最重要的是serialVersionUID,在反序列化时,java会将本地serialVersionUID与数据里的进行对比,只要两者一致的情况下才能成功反序列化,否在会抛出异常错误。

readObject重写

在java中,是否存在反序列化漏洞的关键是readObject方法,只有该方法”可控“才能成功触发java反序列化漏洞。这时候,可能会生出一个疑问,为什么java中的反序列化触发依赖于readObject这个方法?

我们知道,序列化的数据一般只保存属性名、属性值、类等信息。那么实际上,我们能控制的只有值,并不能实际的调用内部的方法,达到我们想要的操作。就以PHP为例,一般情况下,我们构造PHP反序列化漏洞,肯定会用到一个东西:魔术方法,我们一般会在代码中寻找可用的魔术方法,构造出一条可用的攻击链。例如,这里就以__toString()来举一个例子:

1
2
3
4
5
6
<?php
class Test{
function __toString(){
system("whoami");
}
}

熟悉PHP的朋友都知道,当类被当作字符串时,会触发__toString()函数,那么如果有一个类将Test的对象作为字符串输出,就会触发该函数,执行命令。java这里的readObject方法就类似PHP的魔术方法,需要我们去寻找合适的方法,构造攻击链。

下面来给出一个demo进一步了解一下java反序列化漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import  java.io.*;
import java.util.Date;
public class VulnClass implements Serializable {
private String name;
private Date date;
public VulnClass(){}
public VulnClass(String name,Date date){
this.name = name;
this.date = date;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", date=" + date +
'}';
}

private void readObject(java.io.ObjectInputStream stream) throws Exception{
stream.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
import java.util.*;
public class Test implements Serializable {
public static void main(String[] args) throws Exception{
serialize();
deserialize();
}
public static void serialize() throws Exception{
VulnClass user = new VulnClass("monitor",new Date());
FileOutputStream fileOutputStream = new FileOutputStream("test.ser");
ObjectOutputStream obos = new ObjectOutputStream(fileOutputStream);
obos.writeObject(user);
obos.close();
}
public static void deserialize() throws Exception{
FileInputStream fileInputStream = new FileInputStream("test.ser");
ObjectInputStream obis = new ObjectInputStream(fileInputStream);
obis.readObject();
}

}

java-learn2

这里简单调试一下代码,看看是如何导致的:

java-learn4

从调试的堆栈中可以看到,在反序列化时,调用readObject方法时,java会先加载ObjectInputStream类的方法读取反序列化字节的数据,然后通过Method的方法获取反序列化的方法,最终会调用到我们在该类重写的readObject方法,由于在方法内存在系统执行命令(下图第三行),导致反序列化漏洞触发。java-learn3

这里只是以一个Demo演示,实际情况肯定比这复杂的多,而且漏洞构造会用到一些特性,比如Java的反射。

总结

总结一下,java反序列化漏洞的两个关键点:

1.反序列化的类必须实现Serializable接口。

2.在运行的java环境中,有能够利用的readObject方法

因为java一年多没用了,基本上都是从零开始学了。所以这里初步从代码层去理解java反序列的漏洞原理,后续会对java进行深入的学习,分析 ysoserial 这个工具中的payload,进一步学习java利用链。

Reference:

https://xz.aliyun.com/t/6787#toc-3

https://blog.csdn.net/sun1318578251/article/details/106120247

https://github.com/gyyyy/footprint/blob/master/articles/2019/about-java-serialization-and-deserialization.md