Java安全-反序列化

Java反序列化简介

在学习反序列化的漏洞之前,首先需要了解一下Java反序列化的基本概念和用法

关于反序列化的介绍,在之前学习php反序列化的时候已经了解的足够多了,这里就不在过多解释了,简单介绍一下,序列化是一种方便的将对象进行存储和传输的技术,一般情况下在网络通讯或保存数据时,是使用JSON或者XML格式进行传输数据,但这两种格式无法传输对象,利用序列化的方法就可以将Java对象存储到文件或者通过网络传输出去了

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

反序列化:既然有序列化,那就有读取序列化的方法,将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象

简单来说,Java 序列化就是把一个 Java Object 变成一个二进制字节数组 , 即 byte[], Java 反序列化就是把一个二进制字节数组(byte[]) 变回 Java 对象,序列化就是将内存中的数据持久化到硬盘中的过程,反序列化就是将硬盘中的数据恢复到内存中的过程

Java反序列化用法

我们来看看如何将一个Java对象进行序列化

一个Java对象要序列化,必须继承一个特殊的java.io.Serializable接口,这个接口是一个空接口,其中没有定义任何方法,我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法

把一个Java对象变为byte[]数组,需要使用ObjectOutputStream。它负责把一个Java对象写入一个字节流

如下编写一个Person类

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
package Serialize;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Person implements java.io.Serializable {
public String name;
public int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject("This is a object");//在默认的序列化方法执行完成后,又额外写入了一个字符串
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
String message = (String) s.readObject();
System.out.println(message);
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person=new Person("zjm",18);//实例化Person类
ByteArrayOutputStream buff=new ByteArrayOutputStream();//实例化一个字节流输出
ObjectOutputStream out=new ObjectOutputStream(buff);//实例化一个反序列化类流输出
out.writeObject(person);//将实例化的person进行序列化操作
ObjectInputStream in=new ObjectInputStream(new ByteArrayInputStream(buff.toByteArray()));//实例化一个读入序列化数据的反序列化类流
Person newPerson= (Person) in.readObject();//对这个类进行反序列化
}
}

可以看到额外添加的字符串在反序列化的时候被读取,并且成功输出了

因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞

与PHP反序列化的对比

我们接触的第一个反序列化的漏洞一般都是php的反序列化,那么Java的反序列化跟PHP的反序列化有什么相同和不同呢

Java反序列化和PHP反序列化的相同点在于,他们都只能将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象

Java的反序列化一般用到的是一个叫readObject的方法,而PHP则是__wakeup

Java设计readObject 的思路和PHP的wakeup 不同点在于: readObject 倾向于解决“反序列化时如何还原一个完整对象”这个问题,而PHP的__wakeup 更倾向于解决“反序列化后如何初始化这个对象”的问题,简单的说就是在Java反序列化中,我们是可以控制序列化和反序列化时的操作的,但是在PHP反序列化后我们只能控制序列化前和反序列化后的操作