不安全反序列化

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

概述

在存储或传输大量 Java 对象数据时,通常先将数据序列化会更有效率。然后,接收应用程序、活动或提供程序(最终处理数据)将对数据进行反序列化过程。在正常情况下,数据会被序列化,然后在没有任何用户干预的情况下进行反序列化。但是,反序列化过程与其目标对象之间的信任关系可能会被恶意攻击者滥用,例如,恶意攻击者可以拦截和更改序列化对象。这将使恶意攻击者能够执行诸如拒绝服务 (DoS)、权限提升和远程代码执行 (RCE) 等攻击。

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

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

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

  • 利用开发人员错误地认为从自定义类类型反序列化对象是安全的。实际上,任何来自任何类的对象都可能被替换为恶意内容,在最坏的情况下,这些内容可能会干扰相同或其他应用程序的类加载器。这种干扰的形式是注入危险的值,根据类的用途,这些值可能导致数据泄露或帐户接管等后果。

  • 利用设计上被认为不安全的反序列化方法(例如CVE-2023-35669,这是一个允许通过深层链接反序列化向量进行任意 JavaScript 代码注入的本地提权漏洞)。
  • 利用应用程序逻辑中的漏洞(例如CVE-2023-20963,这是一个允许应用程序通过 Android 的 WorkSource 包裹逻辑中的漏洞下载并在特权环境中执行代码的本地提权漏洞)。

影响

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

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

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

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

缓解措施

最佳实践是假设所有序列化数据都是不受信任的且可能存在恶意。为了确保序列化数据的完整性,请对数据执行验证检查,以确保它与应用程序期望的类和格式一致。

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

从 Android 13(API 级别 33)开始,Intent 类中的一些方法已更新,这些方法被认为是处理包裹的更安全的选择,替代了旧的且现已弃用的方法。这些新的类型安全方法,例如getParcelableExtra(java.lang.String, java.lang.Class)getParcelableArrayListExtra(java.lang.String, java.lang.Class)执行数据类型检查以捕获可能导致应用程序崩溃并可能被利用执行权限提升攻击的错误匹配弱点,例如CVE-2021-0928

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

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

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");
}

资源