JAVA 反序列化漏洞入门学习笔记(一)--反序列化简介
JAVA 真的令人头大
参考文章
JAVA 序列化与反序列化
简介
同 PHP/Python 类似,java 序列化的目的是将程序中对象状态转换成以数据流形式,反序列化是将数据流恢复为对象。
此举可以有效地实现多平台之间的通信、对象持久化存储。
序列化实例
import java.io.*;
//定义一个可序列化的类,该类必须实现 java.io.Serializable 接口
class Giao implements java.io.Serializable
{
public String name;
public String motto;
public void saygiao()
{
System.out.println(this.motto);
}
// 自定义 readObject 方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
Runtime.getRuntime().exec("calc.exe");
}
}
//序列化/反序列化
public class SerializeGiao
{
public static void main(String [] args) throws IOException, ClassNotFoundException{
//实例化一个可序列化对象
Giao testClass = new Giao();
testClass.name = "说唱带师";
testClass.motto = "一给我哩 giao giao!";
//序列化
//将序列化后的对象写入到文件
FileOutputStream fos = new FileOutputStream("test.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(testClass);
os.close();
fos.close();
//反序列化
Giao obj = null;
//从文件读取序列化的结果后进行反序列化
FileInputStream fis = new FileInputStream("test.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
obj = (Giao)ois.readObject();
ois.close();
fis.close();
System.out.println(obj.name);
System.out.println(obj.motto);
}
}
//由此可见:人生苦短,我用 Python
序列化结果:
反序列化结果:
序列化条件
一个类的对象要想序列化成功,必须满足两个条件:
-
该类必须实现 java.io.Serializable 或 java.io.Externalizable 接口。
Externalizable 接口继承自 Serializable 接口,实现Externalizable 接口的类完全由自身来控制序列化的行为,而仅实现 Serializable 接口的类可以采用默认的序列化方式 。
class Giao implements java.io.Serializable{}
-
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
实现序列化/反序列化的方法
其中实现序列化与反序列化的两个类为:
-
java.io.ObjectOutputStream
序列化:首先给该类传入一个文件对象(用于写入序列化结果),然后通过调用该类的 writeObject(目标对象) 方法将目标对象写入到文件 -
java.io.ObjectInputStream
反序列化:首先给该类传入一个文件对象(用于读取文件中的序列化结果),然后通过调用该类的 readObject() 方法将其反序列化为目标对象
看到下面代码的执行结果:
输出了 1ndex
,说明反序列化时调用了用户类 Giao 中的 readObject 方法,并且当我注释 in.defaultReadObject();
代码,实际会输出 null
因此,实际上完成反序列化的操作的具体步骤是用户类 Giao 中的 readObject 方法,也就是继承自 Serializable 接口的 readObject 方法
为什么会出现反序列化漏洞
当被反序列化的数据流用户可控时,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码
关键点在于用户自定义类中的 readObject() 方法形成了不安全的类,导致了反序列化安全问题
简单的 demo
漏洞代码:
class RCE implements java.io.Serializable {
public String cmd;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(cmd);
}
public class ToRCE{
public static void main(String[] args) throws IOException, ClassNotFoundException {
RCE testClass = new RCE();
testClass.cmd = "calc";
FileOutputStream fileoutputstream = new FileOutputStream("RCE.ser");
ObjectOutputStream outputstream = new ObjectOutputStream(fileoutputstream);
outputstream.writeObject(testClass);
outputstream.close();
FileInputStream fileinputstream = new FileInputStream("RCE.ser");
ObjectInputStream inputstream = new ObjectInputStream(fileinputstream);
RCE obj = (RCE)inputstream.readObject();
inputstream.close();
}
}
看完上面的代码,是不是觉得 JAVA 反序列化漏洞跟 PHP 反序列化漏洞有些相似
但是怎么会有人写这么愚蠢的代码呢?JAVA 反序列化的高端操作还得看 构造反序列化链
JAVA 反序列化漏洞入门学习笔记(二):JAVA 反射