不安全的反序列化程序

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

資源