不安全的反序列化

OWASP 类别: MASVS-CODE: 代码质量

概览

存储或传输大量 Java 对象数据时,通常先对数据进行序列化会更高效。接收应用、activity 或 provider 在处理数据时会对数据进行反序列化。正常情况下,数据在没有任何用户干预的情况下进行序列化和反序列化。但是,反序列化过程与其预期对象之间的信任关系可能被恶意行为者滥用,他们可以例如拦截和修改序列化对象。这会使恶意行为者能够执行拒绝服务 (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 将额外参数视为 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");
}

资源