不安全的反序列化

OWASP 类别MASVS-CODE:代码质量

概览

在存储或传输大量 Java 对象数据时,通常先对数据进行序列化会更高效。然后,数据将由最终处理数据的接收应用、 activity 或提供程序进行反序列化。在正常情况下,数据会先序列化,然后再反序列化,而无需任何用户干预。不过,恶意方可能会滥用反序列化进程与其预期对象之间的信任关系,例如拦截和更改序列化对象。 这样,恶意方就可以执行拒绝服务 (DoS) 攻击、提权攻击和远程代码执行 (RCE) 攻击。

虽然 Serializable 类是管理 序列化的常用方法,但 Android 有自己的类来处理序列化,称为 Parcel。使用 Parcel 类,对象数据可以序列化为字节 流数据,并使用 Parcelable 接口打包到 Parcel 中。 这样,Parcel 就可以更高效地传输或存储。

不过,在使用 Parcel 类时应仔细考虑,因为它是高效的 IPC 传输机制,但不应用于在本地永久性存储中存储序列化对象,否则可能会导致数据兼容性问题或数据丢失。当需要读取数据时,可以使用Parcelable 接口对 Parcel 进行反序列化,并将其转换回对象数据。

在 Android 中,利用反序列化的主要途径有三种:

  • 利用开发者错误地假设从自定义类类型反序列化对象是安全的。实际上,任何类来源的任何对象都可能被替换为恶意内容,在最坏的情况下,这些恶意内容可能会干扰相同或其他应用的类加载器。这种干扰的形式是注入危险值,根据类的用途,这些值可能会导致数据渗漏或账户接管(攻击)等。
  • 利用设计上被认为不安全的反序列化方法(例 如 CVE-2023-35669,这是一个本地提权漏洞,允 许通过深层链接反序列化途径注入任意 JavaScript 代码)
  • 利用应用逻辑中的漏洞(例如 CVE-2023-20963,这是一个本地提权漏洞,允许应用通过 Android 的 WorkSource parcel 逻辑中的漏洞在特权环境中下载 和执行代码)。

影响

任何反序列化不受信任或恶意序列化数据的应用都可能容易受到远程代码执行或拒绝服务攻击。

风险:反序列化不受信任的输入

攻击者可以利用应用逻辑中缺少 parcel 验证的漏洞来注入任意对象,这些对象在反序列化后可能会强制应用执行恶意代码,从而导致拒绝服务 (DoS)、提权和远程代码执行 (RCE)。

这些类型的攻击可能很隐蔽。例如,应用可能包含一个 intent,该 intent 仅需要一个参数,该参数在经过验证后将被反序列化。如果攻击者在发送预期参数的同时发送第二个意外的恶意额外 参数,则会导致注入的所有数据对象 都被反序列化,因为 intent 将额外参数视为 Bundle。恶意用户可能会利用此行为注入对象数据,这些数据在反序列化后可能会导致 RCE、数据泄露或数据丢失。

缓解措施

作为最佳实践,请假定所有序列化数据都是不受信任的,并且可能是恶意的。为确保序列化数据的完整性,请对数据执行验证检查,以确保其是应用所需的正确类和格式。

一种可行的解决方案是为 java.io.ObjectInputStream 实现前瞻模式。通过修改负责 反序列化的代码,您可以确保 仅反序列化一组明确指定的 类

自 Android 13(API 级别 33)起,Intent 类中更新了多种方法,这些方法被认为是处理 parcel 的旧方法(现已废弃)的更安全替代方案。这些新的类型安全方法(例如 getParcelableExtra(java.lang.String, java.lang.Class)getParcelableArrayListExtra(java.lang.String, java.lang.Class))会执行 数据类型检查,以捕获可能导致应用 崩溃并可能被利用来执行提权攻击(例如 CVE-2021-0928)的不匹配漏洞。

以下示例演示了如何实现安全版本的 Parcel 类:

假设类 UserParcelable 实现 Parcelable 并创建用户数据的 实例,然后将该实例写入 Parcel。然后,可以使用以下 类型安全的 readParcelable 方法读取 序列化的 parcel:

Kotlin

val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)

Java

Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);

请注意,在上面的 Java 示例中,方法内使用了 UserParcelable.CREATOR。此必需参数会告知 readParcelable 方法要预期哪种类型,并且比现已废弃的 readParcelable 方法版本更高效。

具体风险

本部分将汇总符合以下条件的风险:需要采用非标准的缓解策略,或原本已在特定 SDK 级别得到缓解,而为了提供完整信息才列在此处。

风险:不必要的对象反序列化

在类中实现 Serializable 接口会自动导致给定类的所有子类型实现该接口。在这种情况下,某些对象可能会继承上述接口,这意味着不应反序列化的特定对象仍会被处理。 这可能会无意中增加攻击面。

缓解措施

如果某个类继承了 Serializable 接口,则应按照 OWASP 指南 实现 readObject 方法,以 避免该类中的一组对象被反序列化,如下所示:

Kotlin

@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
    throw IOException("Cannot be deserialized")
}

Java

private final void readObject(ObjectInputStream in) throws java.io.IOException {
    throw new java.io.IOException("Cannot be deserialized");
}

资源