Java反序列化原理

发布 : 2019-09-01

Java序列化

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

Java反序列化

反序列化就是将字节序列恢复为Java对象的过程

整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象,因此可以实现多平台之间的通信、对象持久化存储,主要有如下几个应用场景。

HTTP:多平台之间的通信,管理等

RMI:是 Java 的一组拥护开发分布式应用程序的 API,实现了不同操作系统之间程序的方法调用。值得注意的是,RMI 的传输 100% 基于反序列化,Java RMI 的默认端口是1099端口。

JMX:JMX 是一套标准的代理和服务,用户可以在任何 Java 应用程序中使用这些代理和服务实现管理,中间件软件 WebLogic 的管理页面就是基于 JMX 开发的,而 JBoss 则整个系统都基于 JMX 构架。

系列化反序列化基础

序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

一个类的对象能够序列化的成功需要两个条件

  • 该类必须实现 java.io.Serializable 接口

  • 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

漏洞基本原理

简单的反序列化Demo

首先定义对象类Persion,包含两个参数

1
2
3
4
5
6
7
8
//对象类
public class Persion implements java.io.Serializable{
public String name;
public int age;
public void info(){
System.out.println("Name:"+this.name+";\nAge:"+this.age);
}
}

在主类中声明对象,并且将对象序列化为二进制文件,将其存储到硬盘中

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

public class Main{
public static void main(String [] args){
将对象序列化为二进制文件
Persion p = new Persion();
p.name = "Joner";
p.age = 18;
try {

//打开一个文件输入流
FileOutputStream fileOut = new FileOutputStream("D:\\test\\test.db");
//建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(p);
out.close();
fileOut.close();
System.out.printf("保存成功");
}catch(IOException i){
i.printStackTrace();
}
}

进行反序列化

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
import java.io.*;

public class Main{
public static void main(String [] args){
/*从二进制文件中提取对象*/
Persion persion = null;
try{
FileInputStream fileInputStream = new FileInputStream("D:\\test\\test.db");
//建立对象输入流
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
persion = (Persion) inputStream.readObject();
inputStream.close();
fileInputStream.close();
}catch (ClassNotFoundException c){
System.out.println("对象未找到");
c.printStackTrace();
return;
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.println("反序列化对象.......");
System.out.println("Name:"+persion.name);
System.out.println("Age:"+persion.age);
}
}

查看test.db文件的内容可以看见如下内容

其中 AC ED 00 05 是java 序列化内容的特征,其中00 05 是版本信息,base64编码后为ro0AB

反序列化漏洞Demo

在上面的Demo中可以看到,进行反序列化时会调用readObject()方法,如果readObject方法书写不当就会引发漏洞。

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
import java.io.*;

public class Main{
public static void main(String [] args)throws Exception{
Unsafeclass unsafeclass = new Unsafeclass();
unsafeclass.name = "hhhhh";
FileOutputStream fileOutputStream = new FileOutputStream("object");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//将对象写入object文件
objectOutputStream.writeObject(unsafeclass);
objectOutputStream.close();

//从文件中反序列化对象
FileInputStream fileInputStream = new FileInputStream("object");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//恢复对象
Unsafeclass objectFormDisk = (Unsafeclass)objectInputStream.readObject();
System.out.println(objectFormDisk.name);
objectOutputStream.close();
}
}
class Unsafeclass implements Serializable{
public String name;
//重写readObject()方法
private void readObject(java.io.ObjectInputStream inputStream ) throws IOException , ClassNotFoundException{
//执行默认的readObdect()方法
inputStream.defaultReadObject();
//执行打开计算器命令
Runtime.getRuntime().exec("calc.exe");
}
}

程序运行过程为:

  1. UnsafeClass类背序列化进入object文件
  2. 从object文件中恢复对象
  3. 调用被恢复对象的readObject()方法
  4. 命令被执行

这样看感觉并不会有人会这样写readobject()这个方法,而且一些成熟的框架都会有防范反序列化的方法,但仍有很大比例的反序列化漏洞,这主要是使用了不安全的库造成的。上面只是介绍了简单的Java反序列化过程,接下来会有一篇文章介绍反序列化漏洞检测方法以及复现一些经典反序列化漏洞。

参考

https://xz.aliyun.com/t/2041?from=groupmessage

本文作者 : W4rnIn9
原文链接 : http://joner11234.github.io/article/8a58e39c.html
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!