不安全反序列化

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)。

这些类型的攻击可能是微妙的。例如,应用程序可能包含一个仅期望一个参数的 Intent,该参数在经过验证后将被反序列化。如果攻击者发送第二个意外的恶意额外参数以及预期的参数,这将导致所有注入的数据对象被反序列化,因为 Intent 将额外参数视为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");
}

资源