一
介绍
java反序列化是将对象从字节流转换回对象的过程。在Java中,可以使用对象输出流来将Java对象序列化为字节流,并使用对象输入流将字节流反序列化为Java对象。
反序列化的过程是在Java虚拟机中进行的,Java虚拟机会根据字节流的信息重新构造出原始对象,并恢复其状态和数据。反序列化是一种用于数据持久化和跨网络传输对象的常见技术。
二
什么是序列化和反序列化
序列化是将对象转化为数据流的过程,可以用于对象的持久化存储或网络传输。反序列化则是将数据流转化为对象的过程,用于恢复对象的状态。在序列化过程中,对象的状态被转换为字节流,可以保存到文件或通过网络发送。而在反序列化过程中,字节流被还原为对象,恢复了对象的状态。
序列化和反序列化在很多编程语言中都有支持,例如Java的Serializable接口和Externalizable接口。它们允许对象的状态被序列化和反序列化,以便在不同的环境中传输和存储。
1、Java提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。即序列化是指把一个Java 对象变成二进制内容,本质上就是一个byte[]数组。
2、为什么要把 Java 对象序列化呢?
Java对象序列化有以下几个主要的应用场景:
持久化存储:通过将Java对象序列化,可以将对象的状态保存到磁盘文件中,在下次需要时重新读取并反序列化,恢复对象的状态。这在需要长期保存对象的状态,或者需要在不同的系统之间传递对象时非常有用。
网络传输:通过将Java对象序列化为字节流,可以在网络中传输对象的状态。这在分布式系统中非常常见,可以将对象通过网络传递给其他远程节点,实现远程方法调用或者分布式计算。
缓存:将Java对象序列化并存储在缓存中,可以有效地减少数据库或其他耗时资源的访问。当需要使用对象时,可以直接从缓存中读取并反序列化,提高系统的性能和响应速度。
需要注意的是,Java对象序列化并不适用于所有情况。例如,对于涉及安全性的对象,特别是与密码、密钥等敏感信息相关的对象,需要谨慎使用序列化,避免敏感信息的泄露。
同时,在序列化中可能存在版本兼容性问题,当对象的结构发生改变时,可能会导致反序列化失败。因此,在使用Java对象序列化时,需要注意选择合适的场景并进行适当的安全和版本管理。
3、将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,即把一个二进制内容(也就是byte[]数组)变回 Java 对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java 对象,或者从网络上读取byte[]并把它“变回”Java对象。也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
4、整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。5Java 的序列化机制仅适用于 Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如 JSON。
三
序列化要求
在Java中,要实现对象的序列化,需要满足以下要求:
1、类必须实现 java.io.Serializable 接口:该接口是一个标记接口,没有任何方法需要实现。通过实现该接口,表示类的对象可以被序列化和反序列化。
2、对象的所有成员变量都必须是可序列化的:如果一个类的成员变量是引用类型,那么该成员变量的类也必须是可序列化的。这可以通过实现 java.io.Serializable 接口来满足。
3、不需要序列化的成员变量需要使用 transient 关键字修饰:transient 关键字用于标记那些不需要序列化的成员变量。当一个成员变量被标记为 transient 后,在序列化过程中将被忽略。
4、序列化的类需要提供一个无参构造方法:在反序列化过程中,会使用无参构造方法创建对象。
需要注意的是,如果采用默认的Java序列化机制,可以直接使用 ObjectInputStream 和 ObjectOutputStream 类来进行对象的序列化和反序列化。
如果需要自定义序列化行为,可以通过实现 java.io.Externalizable 接口来实现。该接口要求实现 writeExternal 和 readExternal 方法,用于手动指定对象的序列化和反序列化过程。
四
序列化步骤
1、创建一个实现Serializable接口的类,该类的对象需要被序列化。
2、使用ObjectOutputStream类创建一个输出流对象。
3、使用输出流对象的writeObject方法将对象写入流中,即序列化对象。
4、将序列化后的字节流保存到文件、发送给其他应用程序等。
5、使用ObjectInputStream类创建一个输入流对象。
6、使用输入流对象的readObject方法读取字节流并将其转换为对象,即反序列化对象。
7、对反序列化后的对象进行操作。
代码示例:
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
// 创建一个需要序列化的对象
MyClass obj = new MyClass("John", 25);
try {
// 序列化对象并保存到文件
FileOutputStream fos = new FileOutputStream("obj.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
fos.close();
System.out.println("对象已序列化并保存到文件");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化对象
MyClass newObj = null;
try {
// 从文件中读取序列化的对象
FileInputStream fis = new FileInputStream("obj.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
newObj = (MyClass) ois.readObject();
ois.close();
fis.close();
System.out.println("从文件中读取的对象:" + newObj);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass implements Serializable {
private String name;
private int age;
public MyClass(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "MyClass{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
*左右滑动查看更多
在这个示例中,首先创建了一个需要被序列化的MyClass对象,并且通过ObjectOutputStream将它写入到文件中。然后,通过ObjectInputStream从文件中读取序列化的对象,并将其转换为MyClass类型的对象。
五
反序列化步骤
1、创建一个ObjectInputStream对象,用于从文件或网络中读取序列化的对象数据。
2、使用ObjectInputStream对象的readObject()方法读取序列化的对象。该方法会返回一个Object类型的对象。
3、将读取到的Object对象强制转换为正确的类型。
4、关闭ObjectInputStream对象。
以下是一个简单的Java反序列化的代码示例:
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
try {
// 创建一个ObjectInputStream对象
FileInputStream fileInputStream = new FileInputStream("data.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 使用ObjectInputStream对象的readObject()方法读取序列化的对象
Object object = objectInputStream.readObject();
// 将读取到的Object对象强制转换为正确的类型
MyClass myObject = (MyClass) object;
System.out.println("Deserialized Object: " + myObject);
// 关闭ObjectInputStream对象
objectInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
*左右滑动查看更多
上述代码中,首先创建一个FileInputStream对象来读取序列化后的对象数据。然后,创建一个ObjectInputStream对象,并将FileInputStream对象传递给它。接着,使用ObjectInputStream对象的readObject()方法读取序列化的对象。最后,将读取到的Object对象强制转换为正确的类型,然后进行后续的操作。在代码的末尾,记得关闭ObjectInputStream对象。
六
序列化特征
Java序列化是指将对象转换为字节流,以便可以将其存储在内存或磁盘上,或通过网络传输。
Java序列化具有以下特征:
a、保存对象状态:序列化可以将对象的状态保存为字节流,以便在需要时可以重新创建相同的对象,并恢复其之前的状态。
b、跨平台性:Java序列化的字节流是与平台无关的,这意味着可以在不同的平台上序列化和反序列化对象。
c、可扩展性:Java序列化允许将对象的状态进行更改和扩展,而无需更改序列化和反序列化的代码。
d、支持实例变量:Java序列化可以保存对象的实例变量(即成员变量),以便可以在恢复对象时重新初始化这些变量。
e、不支持静态变量:Java序列化不会保存对象的静态变量,因为静态变量属于类而不是对象。
总的来说,Java序列化提供了一种方便的方式来保存和传输对象的状态,并且具备可扩展性和跨平台性。
七
安全性
因为 Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java 代码,从而导致严重的安全漏洞。
实际上,Java 本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过 JSON 这样的通用数据结构来实现,只输出基本类型(包括 String)的内容,而不存储任何与代码相关的信息。
八
反序列化漏洞的攻击流程
1、构造恶意的序列化数据:攻击者需要构造一个特殊的序列化数据,其中包含恶意代码或攻击载荷。攻击载荷可以是任何攻击者期望执行的代码,例如远程代码执行、文件删除等。
2、传递序列化数据到目标应用程序:攻击者将构造的恶意序列化数据发送给目标应用程序,通常通过网络传输。
3、应用程序反序列化数据:目标应用程序接收到序列化数据后,使用Java反序列化机制将其转换为对象。在反序列化的过程中,会调用被攻击类的readObject()方法。
4、执行恶意代码:当反序列化时,如果readObject()方法中存在漏洞,攻击者的恶意代码将被执行。这可能导致任意代码执行、远程代码执行、拒绝服务等攻击行为。
代码示例:
下面是一个简单的示例,展示了一个存在反序列化漏洞的类:
import java.io.*;
public class VulnerableClass implements Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
// 漏洞:未对反序列化数据进行过滤和验证
Runtime.getRuntime().exec("calc.exe"); // 执行恶意代码
}
}
*左右滑动查看更多
在上述示例中,readObject()方法存在漏洞,攻击者可以构造一个恶意的序列化数据来触发该方法中的恶意代码执行。
九
java安全开发防御建议
要避免Java序列化和反序列化安全问题,可以采取以下几个注意事项:
1、避免使用Java的默认序列化机制:默认的Java序列化机制存在安全风险,因此最好避免使用它。可以考虑使用其他序列化框架,如JSON、XML等。
2、显式声明serialVersionUID:在自定义的可序列化类中,显式声明serialVersionUID是一个好习惯。这个值作为序列化版本的唯一标识符,可以避免由于类的修改导致的反序列化失败或者攻击。
3、输入验证和过滤:在反序列化之前,对输入数据进行验证和过滤。确保输入数据的合法性,防止恶意数据触发安全漏洞。
4、使用安全的序列化框架:如果必须使用Java的默认序列化机制,可以使用安全的序列化框架来限制反序列化所需的类和方法,以及防止远程类加载。
5、最小化可序列化的类:尽量减少可序列化的类的数量,只将必要的字段进行序列化,以降低攻击面。
6、序列化和反序列化的类应该实现Serializable接口:确保可序列化的类中都明确实现了Serializable接口,避免因为漏掉这一步骤导致的序列化问题。
7、在反序列化过程中实施安全策略:在反序列化过程中,可以通过安全策略来限制可序列化类的访问和调用,只允许特定的类进行反序列化操作。
8、定期更新和修补:及时关注和了解关于序列化和反序列化漏洞的最新信息,及时更新和修补应用程序。
总的来说,在Java安全开发中需要谨慎处理序列化和反序列化操作,将安全性作为设计和实施的考虑因素,并采取相应的安全措施来保护应用程序免受攻击。
十
JAVA反序列化防御
Java反序列化漏洞是一种常见的安全问题,通过在网络传输或持久化过程中攻击序列化的对象,并在反序列化时执行恶意代码,从而达到攻击目的的攻击方式。相关的防御措施如下:
1、序列化对象需要进行安全合规评估,对于没有进行安全合规评估的序列化对象,应禁止在系统中使用。
2、序列化对象需要进行身份验证,验证其来源是否可信,有必要时可以对它进行数字签名验证。
3、使用组件或框架提供的反序列化黑名单机制,限制不受信任的类的反序列化,并进行相应的日志记录。
4、在实现类的反序列化方法时,将不信任的数据作为异常处理,不要直接进行反序列化操作,同时对反序列化对象进行快照备份,以便于源程序和反序列化可比较。
5、禁止在反序列化对象的构造函数中使用外部数据进行初始化,避免数据的安全问题产生。
6、避免在同一进程内使用不同版本的序列化框架或组件,以免产生兼容性问题导致安全问题产生。
通过上述安全防范措施,可以在一定程度上防止Java反序列化漏洞。同时,还需不断关注相关漏洞的相关信息和最新修复措施,及时更新和调整相应的防范措施。
原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| JAVA反序列化用法科普
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论