不安全的反序列化

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 验证这一点,注入任意对象。这些对象在反序列化后可能会强制应用执行恶意代码,从而可能导致拒绝服务 (DoS)、特权提升和远程代码执行 (RCE)。

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

缓解措施

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

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

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

资源