安全でない逆シリアル化

OWASP カテゴリ: MASVS-CODE: コード品質

概要

大量の Java オブジェクト データを保存または転送する場合は、最初にデータをシリアル化する方が効率的なことがよくあります。データは、最終的にデータを処理する受信側のアプリケーション、Activity、プロバイダによって逆シリアル化プロセスを経ます。通常、データはユーザーの介入なしでシリアル化されてから逆シリアル化されます。ただし、デシリアライズ プロセスとその対象オブジェクト間の信頼関係は、悪意のある行為者によって悪用される可能性があります。たとえば、シリアル化されたオブジェクトを傍受して変更する可能性があります。これにより、悪意のある行為者がサービス拒否(DoS)、権限昇格、リモートコード実行(RCE)などの攻撃を行う可能性があります。

Serializable クラスはシリアル化を管理する一般的なメソッドですが、Android には Parcel というシリアル化を処理する独自のクラスがあります。Parcel クラスを使用すると、オブジェクト データをバイト ストリーム データにシリアル化し、Parcelable インターフェースを使用して Parcel にパックできます。これにより、Parcel をより効率的に輸送または保管できます。

ただし、Parcel クラスは高効率の IPC 転送メカニズムとして使用されることを想定していますが、シリアル化されたオブジェクトをローカル永続ストレージに保存するために使用すると、データの互換性の問題や損失につながる可能性があるため、使用する際は慎重に検討する必要があります。データを読み取る必要がある場合は、Parcelable インターフェースを使用して Parcel を逆シリアル化し、オブジェクト データに戻すことができます。

Android での逆シリアル化を悪用する主なベクトルは 3 つあります。

  • カスタム クラス型から派生したオブジェクトの逆シリアル化は安全であるというデベロッパーの誤った想定を利用します。実際には、任意のクラスによって取得された任意のオブジェクトが、悪意のあるコンテンツに置き換えられる可能性があります。最悪の場合、同じアプリケーションまたは他のアプリケーションのクラスローダに干渉する可能性があります。この干渉は、クラスの目的に応じて、たとえばデータ流出やアカウントの乗っ取りにつながる可能性のある危険な値を挿入する形で行われます。
  • 設計上安全でないとみなされる逆シリアル化メソッドの悪用(たとえば、ディープリンクの逆シリアル化ベクトルを介して任意の JavaScript コードの挿入を可能にするローカル権限昇格の不具合である CVE-2023-35669 など)
  • アプリのロジックの欠陥を悪用する(たとえば、CVE-2023-20963。Android の WorkSource パーセル ロジックの欠陥を介して、アプリが特権環境内でコードをダウンロードして実行できるローカル権限昇格の欠陥)。

影響

信頼できないシリアル化されたデータや悪意のあるシリアル化されたデータを逆シリアル化するアプリケーションは、リモートコード実行攻撃やサービス拒否攻撃に対して脆弱になる可能性があります。

リスク: 信頼できない入力の逆シリアル化

攻撃者は、アプリのロジック内のパーセル検証の欠如を悪用して任意のオブジェクトを挿入できます。このオブジェクトが逆シリアル化されると、アプリが強制的に悪意のあるコードを実行し、サービス拒否攻撃(DoS)、権限昇格、リモートコード実行(RCE)が発生する可能性があります。

このような攻撃は、気づきにくい場合があります。たとえば、アプリケーションには、1 つのパラメータのみを想定するインテントが含まれている場合があります。このパラメータは、検証後に逆シリアル化されます。攻撃者が予期されるパラメータとともに予期しない悪意のある追加パラメータを送信すると、インテントが追加パラメータを Bundle として扱うため、挿入されたすべてのデータ オブジェクトが逆シリアル化されます。悪意のあるユーザーがこの動作を利用して、逆シリアル化されると RCE、データ侵害、損失につながる可能性のあるオブジェクト データを挿入する可能性があります。

リスクの軽減

効果的な手法として、シリアル化されたすべてのデータは信頼できない可能性があり、悪意のある可能性があると想定してください。シリアル化されたデータの完全性を確保するには、データがアプリケーションで想定される正しいクラスと形式であることを確認するために、データに対して検証チェックを実行します。

実現可能な解決策としては、java.io.ObjectInputStream ライブラリに先読みパターンを実装することが考えられます。逆シリアル化を担当するコードを変更することで、インテント内で逆シリアル化されるクラスのセットを明示的に指定できます。

Android 13(API レベル 33)以降では、Intent クラス内でいくつかのメソッドが更新されました。これらのメソッドは、Parcel を処理するための古いメソッド(現在は非推奨)に代わる、より安全な代替手段と見なされています。getParcelableExtra(java.lang.String, java.lang.Class)getParcelableArrayListExtra(java.lang.String, java.lang.Class) などの新しい型安全なメソッドは、データ型のチェックを行い、アプリケーションのクラッシュや、CVE-2021-0928 などの権限昇格攻撃に悪用される可能性のある不一致の脆弱性を検出します。

次の例は、Parcel クラスの安全なバージョンを実装する方法を示しています。

クラス UserParcelableParcelable を実装し、ユーザーデータのインスタンスを作成して 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 インターフェースを実装すると、そのクラスのすべてのサブタイプが自動的にインターフェースを実装します。このシナリオでは、一部のオブジェクトが上記のインターフェースを継承する可能性があります。つまり、逆シリアル化を意図していない特定のオブジェクトが処理される可能性があります。これにより、攻撃対象領域が意図せず拡大する可能性があります。

リスクの軽減

OWASP のガイダンスに従って、クラスが Serializable インターフェースを継承する場合、クラス内のオブジェクトのセットが逆シリアル化されないように、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");
}

リソース