不安全的反序列化程序

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 Parcel 邏輯中,在具有特殊權限的環境中下載及執行程式碼)。

影響

任何會將不受信任或惡意的序列化資料反序列化的應用程式,都可能遭受遠端程式碼執行或拒絕服務攻擊。

風險:不受信任的輸入內容序列化

攻擊者可以利用應用程式邏輯中缺少包裹驗證程序的手法,插入任意物件。在去序列化後,可能會強制要求應用程式執行惡意程式碼,進而導致阻斷服務 (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");
}

資源