Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
TheLastBundleMismatch — CVE-2023-45777のライトアップとエクスプロイト。Android 13のAccountManagerService内のIntent検証を、「Lazy Bundle」緩和策にもかかわらずバイパスする手法。 | Kitploit
ツール/GitHubGitHub/michalbednarski/thelastbundlemismatch
Androidセキュリティ脆弱性分析エクスプロイトバイナリ解析論文と研究
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

CVE-2023-45777のライトアップとエクスプロイト。Android 13のAccountManagerService内のIntent検証を、「Lazy Bundle」緩和策にもかかわらずバイパスする手法。

リポジトリを見る

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
10114522年前Kitploit レビュー済み

Mysterious patch

今回は、Android セキュリティ ブルテンでCVE-2023-45777の修正として登場したパッチから始めましょう。```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();

  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    
  • root@kitploit:~
           Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
           if (intent != null && intent.getClass() != Intent.class) {
               return false;
           }
    
root@kitploit:~
この問題について私に尋ねるほど興味を持った人はほとんどいませんでした。以前はヒントを返信していましたが、今回この問題の完全な解説を公開します

しかし、まずこのパッチで何が起きているのかについて、いくつか背景を説明しましょう

これは[`checkKeyIntent()` メソッド](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d)の変更です。このメソッドは、アプリが提供する `Intent` がシステムにとって(システムの権限を使用して)起動しても安全であることを確認するために、複数のチェックを実行します

まず、このメソッドは `checkKeyIntentParceledCorrectly()` を使用します。これは、チェック対象の `Bundle` をシリアライズしてから再度デシリアライズし、その前に `Bundle` から取得した `Intent` が、そのようなサイクルの後に `Bundle` から取得した `Intent` と一致するかをチェックします。`Intent` の起動は検証を実行するプロセスとは異なるシステムアプリのプロセスで行われるため、以前は[`AccountManagerService` 内での検証中は安全に見えるが、次のプロセスに送信された後は異なる `Intent` を含む `Bundle` を構築することが可能でした](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482)。これは、そのような状況を検出するために、`Bundle` を次のプロセスに送信することをシミュレートします

`checkKeyIntentParceledCorrectly()` の後には `bundle.getParcelable()` 呼び出しがあります。このパッチは、任意のオブジェクトを構築できる非推奨バージョンから、デシリアライズされようとしているオブジェクトが2番目のパラメータで指定された型であることを検証するバージョンに切り替えます

型パラメータ付きのそのバージョンは、より大規模な `Parcel`/`Bundle` の堅牢化の一環として Android 13 で導入されました。具体的には、Android 13 より前では、`Bundle` がプロセス間で送信されるとき、項目にアクセスされるまでシリアライズされたデータ全体の生のコピーを保持し、その時点で全ての値がデシリアライズされていました。現在では、`Bundle` を受信した後に何らかの値に初めてアクセスするとき、`String` キーとプリミティブ型の値のみがデシリアライズされ、非プリミティブ値は `LazyValue` として残されます。`LazyValue` は、シリアライズ/デシリアライズのロジックが不一致であっても、そのような不一致が他のエントリに影響しないように、シリアライズされたデータの一部として長さを保持します

本題に入る前に、`LazyValue` を見てみましょう。そのソースコードには[データ構造を説明する素晴らしいコメントがあります](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
                     |   4B   |   4B   |
mSource = Parcel{... |  type  | length | object | ...}
                     a        b        c        d
length = d - c
mPosition = a
mLength = d - a

mPosition と mLength は、元の Parcel 内の LazyValue データ全体の位置を表し、type と length の両方を含みます。「length」(先頭に「m」がないもの)は、Parcel に書き込まれた長さの値を指し、ヘッダー(type と length)は含みません。

LazyValue を含む Bundle が別のプロセスに転送される場合、type と length フィールドを含む LazyValue 全体が Bundle.mParcelledData から宛先の Parcel にそのままコピーされます。

LazyValue で表される Bundle の項目にアクセスすると、Parcel は mPosition まで巻き戻され、readValue() が呼び出されます。bundle.getParcelable() に型引数が渡された場合、それは readValue() に伝播され、アンパーセルされようとしている型が期待される型であることを保証するとともに、アンパーセル後に、アンパーセルされた値の型が期待される型であることを検証します。アンパーセル後、LazyValue は置き換えられるため、次回 Bundle が Parcel に書き込まれる際には、値は再び writeValue() を通じてシリアライズされます。

型付きの Bundle.get*()/Parcel.read*() パラメータの使用は、主に Parcel.readParcelableList() などのメソッドに関連します。これは ArrayList を返し、Java の型消去により、List<SomeParcelableType> field = parcel.readParcelableList(); のようなことを行ったとしても、<SomeParcelableType> の部分は実行時に強制されず、そのような List にはシステム内で利用可能な任意の Parcelable クラスが含まれる可能性があり、したがってシステム内で利用可能なすべての createFromParcel/writeToParcel が、そのような List を含む型のシリアライズ/デシリアライズの一部として使用される可能性があります。

また、Android Security and Privacy チームによるこれらのメカニズムの導入に関するプレゼンテーション(スライド、ビデオ)もご覧になることをお勧めします。

ただし、ここでは返されたオブジェクトの型も明示的にチェックしているため、型付きバージョンの使用は冗長であるように思われます。では、ここで何が起きていて、どのような脆弱性が修正されているのでしょうか?

副作用

最初からパッチをもう一度見てみましょう

  • "intent" キーの下でデシリアライズされた値が Intent の場合
    • システムが起動しても安全なコンポーネントを指していることが検証されます
    • Intent オブジェクトから不一致が引き起こされる可能性がある場合、はるかに大きな問題になります
  • デシリアライズされている値が Intent でない場合
    • 悪意のあることを行うには、別のプロセスに送信された後に Bundle 内に Intent が存在する必要がありますが、Parcelable の型は不一致が発生する可能性のあるオフセットよりも前に保存されており、LazyValue の長さプレフィックスにより、writeToParcel/createFromParcel の不一致が発生した場合に後続のキーと値のペアを変更することが防止されます

では、型引数なしの bundle.getParcelable(AccountManager.KEY_INTENT) の呼び出しが、ここでどのような危険なことを引き起こす可能性があるのでしょうか?

[答えは次の段落にあります。読む前に推測してみてください。もし私にファーソナがいたら、ここにアートを置く場所になるでしょう]

答えは、別のキーの下に保存され、次のプロセスにそのまま渡される LazyValue の生データを実際に変更する、無関係な createFromParcel() を呼び出すことです。

提供された Parcel に対して実際に writeInt() を呼び出すことができる createFromParcel() 実装があります。

しかし、これは writeInt が誤って配置されているためではなく、無制限のリフレクションによるものです。具体的には、PackageParser 内に次のコードがあります:```java final Class cls = (Class) Class.forName(componentName); final Constructor cons = cls.getConstructor(Parcel.class);

intentsList = new ArrayList<>(N); for (int i = 0; i < N; ++i) { intentsList.add(cons.newInstance(in)); }

root@kitploit:~
We can have `Parcel` object which was passed to `createFromParcel` passed to any available in system `public` constructor that accepts single `Parcel` argument

And then [we have following code](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=51-56;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb):```java
public PooledStringWriter(Parcel out) {
    mOut = out;
    mPool = new HashMap<>();
    mStart = out.dataPosition();
    out.writeInt(0); // reserve space for final pool size.
}

コンストラクタは、渡された Parcel に対して writeInt(0) を呼び出しますが、悪用を複雑にする点がいくつかあります。

まず、ソースコードには直接見えませんが、newInstance() が呼び出された直後にキャストが実行され、ClassCastException がスローされます。

例外を飲み込む

createFromParcel の実行中に、別のクラスの createFromParcel を try ブロック内で呼び出し、その後、捕捉した例外を伝播させずに失敗させる何かが必要でした。

ここが、このエクスプロイトが純粋なAOSPでは実際には動作しない部分であり、Samsung固有のクラスを使用しました。

このリポジトリに、そのクラスの関連部分のコピーを含めました

このリポジトリには、それをAOSPに統合するスクリプト も含まれているため、テスト用に実行できます(AOSPチェックアウトへのパスを引数として渡します。例: ./make-aosp-buggy.sh /path/to/aosp)。レポート冒頭で説明した変更を元に戻し、このエクスプロイトをAOSPビルドに対して実行してください。

以前、例外の飲み込みにはAOSPの OutputConfiguration クラスを使用しました。Android 13より前では、createFromParcel() 内で例外を飲み込み、他の Parcelable の構築を許可すること自体が脆弱性でしたが、SemImageClipData の場合、これらのAndroidバージョンには例外の飲み込みは存在しませんでした。

ただし、SemImageClipData と以前使用した OutputConfiguration の間には重要な違いがあります。SemImageClipData は例外を捕捉するものの、依然として非nullオブジェクトを返し、それが後で別の型にキャストされると、ClassCastException がトリガーされます。これは私たちが回避しようとしているものです。

Javaの型消去が逆襲する

Javaの型消去は、ジェネリックメソッドが呼び出し元が使用するジェネリック型を実際には認識しないことを意味します。これは通常、悪用に役立っていました。```java // When we read some List, this actually didn't check if list contains only SomeParcelableType List myList = sourceParcel.readParcelableList();

// Above is why Android 13 has introduced typed methods that enforce type at runtime List myList = sourceParcel.readParcelableList(SomeParcelableType.class);

// If untyped method was used when reading, list can contain non-SomeParcelableType // items and they would be written without errors targetParcel.writeParcelableList(myList, 0);

// However if List contains non-SomeParcelableType item, this would throw during item access // (That however commonly didn't happen if we used Parcelable object only as container in gadget chain) SomeParcelableType myItem = myList.get(0);

root@kitploit:~
今回も型消去は我々の味方にはならなかった。まず、リフレクションを通じて実際にコンストラクタを呼び出すメソッドがあった。```java
private static <T extends IntentInfo> ArrayList<T> createIntentsList(Parcel in) {
    // ...
    final ArrayList<T> intentsList;
    // ...
    intentsList.add(cons.newInstance(in));
    // ...
    return intentsList;
}

このメソッドはジェネリックパラメータ T を持ちます。呼び出し側がどのパラメータ型を使用したかは問題ではありません。ただし、このメソッドの宣言に <T extends IntentInfo> があるため、newInstance() 呼び出しの行は intentsList.add((IntentInfo) cons.newInstance(in)); になります。newInstance() は Object を返し、ArrayList.add() は引数として Object を受け付けるにもかかわらずです。これにより、その呼び出しを、Exception を吸収する何らかの Parcelable でラップする必要が生じました。

次に、bundle.getParcelable() 呼び出しがあります。```java @Deprecated @Nullable public T getParcelable(@Nullable String key) { unparcel(); Object o = getValue(key); if (o == null) { return null; } try { return (T) o; } catch (ClassCastException e) { typeWarning(key, o, "Parcelable", e); return null; } }

root@kitploit:~
デシリアライズ処理は`getValue()`呼び出しによって実行され、実際には`createFromParcel()`呼び出しにつながります。そこで`ClassCastException`が発生した場合、それは捕捉されません。`getValue()`は、[`parcel.readValue()`](https://developer.android.com/reference/android/os/Parcel#readValue(java.lang.ClassLoader))を通じてこのキーに対してデシリアライズされた値をそのまま返すようになりました。

しかし、`SemImageClipData`を値として`try`-`catch`内に置いた場合、`T`へのキャストを試みることになります。この場合の`T`は、メソッドのジェネリック宣言で宣言されているように`Parcelable`です。呼び出し元はこのメソッドを`T`が`Intent`であるジェネリックとして使用しますが、`getParcelable()`はそれを知らないため、`Intent`へのキャストは呼び出し元で行われ、したがって`ClassCastException`は`try`の外でスローされます。

しかし、`SemImageClipData`を`Parcelable[]`配列でラップすると、`getParcelable()`内の`T`へのキャストが`Parcelable[]`を`Parcelable`にキャストできず、`try`内で`ClassCastException`をスローします。その`Exception`はログに記録され、`null`が返されて`checkKeyIntent()`によって受け入れられます。

# レイアウト

次に、`Bundle`内の要素を整列させて、`writeToParcel`/`createFromParcel`サイクル後にその内容が私たちが準備したものになるようにする必要があります。

しかし、トリガーが`createFromParcel`が以前に`writeToParcel`が行ったよりも多いまたは少ないデータを読み取ることである典型的な「`Bundle` FengShui」とは異なり、ここでは`writeInt(0)`が非デシリアライズの`LazyValue`の一部を上書きします。

では、`Bundle.mParcelledData`が`AccountManagerService`によって最初にアンパーセルされるときの様子は次のとおりです(オフセットは`system_server`にアタッチされたデバッガーを通じて`dataPosition()`を呼び出して取得)。

<table>
<tr><th>オフセット</th><th>値</th><th>備考</th></tr>
<tr><td>0</td><td>3</td><td>キーと値のペアの数</td></tr>
<tr><td>4</td><td>"intent"</td><td><code>Bundle</code>内の最初のキー。<code>getParcelable(AccountManager.KEY_INTENT)</code>によってアクセスされるもの</td></tr>
<tr><td>24</td><td>16</td><td>最初の<code>LazyValue</code>はここから始まる。型は<code>VAL_PARCELABLEARRAY</code></td></tr>
<tr><td>28</td><td>340</td><td><code>LazyValue</code>の宣言された長さ。<code>Bundle</code>内の次のキーを見つけるために使用される。私たちの<code>LazyValue</code>は読み取り後、実際にはこのサイズにならないが、<code>LazyValue.apply</code>は<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/Parcel.java;l=4501-4505;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">それを<code>Slog.wtfStack()</code>を通じて報告</a>し、<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Slog.java;l=230-235;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">それはスローしない</a></td></tr>
<tr><td>32</td><td>1</td><td><code>Parcelable[]</code>配列の長さ。配列には項目が1つだけあり、<code>bundle.getParcelable()</code>内の<code>try</code>ブロック内で<code>ClassCastException</code>が発生するように存在する</td></tr>
<tr><td>36</td><td>"com.samsung.android.<br>content.clipboard.data.<br>SemImageClipData"</td><td><code>Parcelable</code>クラスの名前。これはExceptionを飲み込むラッパークラス</td></tr>
<tr><td>160</td><td>2</td><td><code>createClipBoardData()</code>によって使用される型タグ</td></tr>
<tr><td>164</td><td></td><td><code>SemImageClipData</code>スーパークラスコンストラクタによって読み取られる項目(<code>readParcelable()</code>呼び出しを含むが、それは<code>try</code>ブロックの外で発生する)。実際には重要ではないが、<code>createFromParcel()</code>の興味深い部分に到達する前にこれらを通過する必要がある</td></tr>
<tr><td>252</td><td></td><td><code>SemImageClipData.readFromSource()</code>によって読み取られるデータ</td></tr>
<tr><td>272</td><td>"android.content.pm.<br>PackageParser&#36;Activity"</td><td><code>mExtraParcelFd = in.readParcelable()</code>によって読み取られる<code>Parcelable</code>の名前。型が一致しないが、キャストが発生する前にExceptionがとにかくスローされる</td></tr>
<tr><td>360</td><td></td><td><code>PackageParser$Component</code>の<code>className</code> &amp; <code>metadata</code>フィールド</td></tr>
<tr><td>368</td><td>1</td><td><code>createIntentsList()</code>内の項目数</td></tr>
<tr><td>372</td><td>"android.os.<br>PooledStringWriter"</td><td><code>Class.forName().getConstructor(Parcel.class).newInstance()</code>を通じてインスタンス化されるクラスの名前。この位置で最初の<code>LazyValue</code>が終了するが、<code>readValue()</code>が末尾に到達しなかったため、その解析は続行される。また、初期の<code>unparcel()</code>中に<code>Bundle</code>内の2番目のキーとして解釈される</td></tr>
<tr><td>436</td><td>4</td><td>2番目の<code>LazyValue</code>はここから始まる。この4は<code>VAL_PARCELABLE</code>であり、<code>Parcel.isLengthPrefixed()</code>は<code>true</code>を返す。この値は後に<code>PooledStringWriter</code>コンストラクタによって上書きされ、その後Exceptionがスローされて<code>getParcelable(AccountManager.KEY_INTENT)</code>が終了する</td></tr>
<tr><td>440</td><td>240</td><td>型が<code>VAL_PARCELABLE</code>と宣言された<code>LazyValue</code>の長さ。これは次のエントリの位置と、再シリアライズ中にターゲット<code>Bundle</code>にコピーする必要があるデータ量を決定するために使用される。この<code>LazyValue</code>は実際にはアンパーセルされず、生データコンテナとして使用される</td></tr>
<tr><td>684</td><td>"1&y~pw"</td><td rowspan="2">3番目のキーと値のペア。キーはJavaの<code>hashCode()</code>が以前に使用されたものより上になるようにランダムに生成される(<code>ArrayMap</code>内に格納された項目はキーの<code>hashCode()</code>の昇順でソートされ、それが<code>Bundle</code>の項目が<code>Parcel</code>に書き込まれる順序である)。このキーと値のペアは、書き込まれるペアの総数を増やすためだけにここに存在する。読み取られるペアの数もその数になるためである。ただし、このペアは実際には読み取られない</td></tr>
<tr><td>704</td><td>-1 (<code>VAL_NULL</code>)</td></tr>
</table>

次に、Bundleが再度シリアライズされるとき、次のようになります:

<table>
<tr><th>オフセット</th><th>値</th><th>備考</th></tr>
<tr><td>0</td><td>3</td><td>キーと値のペアの数</td></tr>
<tr><td>4</td><td>"intent"</td><td><code>Bundle</code>内の最初のキー</td></tr>
<tr><td>24</td><td>16</td><td><code>VAL_PARCELABLEARRAY</code>。以前にデシリアライズされた<code>Parcelable[]</code>配列が再びシリアライズされている</td></tr>
<tr><td>28</td><td>196</td><td><code>LazyValue</code>の長さ。これはラップされた<code>SemImageClipData</code>オブジェクト。この長さは私のモック<code>SemImageClipData</code>を使用した実行から取得されたものであり、したがってこの時点から提示されるオフセットは実際のSamsungデバイスに現れるものとは一致しない。ただし、この<code>LazyValue</code>は再びデシリアライズされないため、エクスプロイトの実行には関係ない</td></tr>
<tr><td>224</td><td>"android.os.<br>PooledStringWriter"</td><td><code>Bundle</code>内の2番目のキー</td></tr>
<tr><td>288</td><td>0</td><td>2番目の<code>LazyValue</code>はここから始まる。<code>"android.os.PooledStringWriter"</code>キーの下の項目はアクセスされなかったため、この<code>LazyValue</code>は元のデータからコピーされている。ただし、型タグは<code>PooledStringWriter</code>コンストラクタによって行われた<code>writeInt(0)</code>呼び出しによって上書きされ、ターゲットプロセスに到達すると、これはもはや<code>LazyValue</code>として解釈されない</td></tr>
<tr><td>292</td><td>240</td><td>これは元の<code>Bundle</code>からコピーされた2番目の<code>LazyValue</code>の長さだった。ただし、型タグが<code>writeInt(0)</code>(これは<code>VAL_STRING</code>)で上書きされたため、この値は現在<code>readString()</code>を通じて読み取られている。以前は<code>LazyValue</code>の場合、長さはバイトで表されていたが、現在は<code>String</code>の場合、これは2バイト文字で表される。ソース<code>Parcel</code>にはそのための十分なデータがないため、<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/Parcel.cpp;l=2221-2226;drc=4d6b008243a5b1b1fb4e725e37e14651a24a4a4d">ネイティブの<code>parcel->readString16Inplace()</code>は長さを読み取った後に失敗する</a>が、それはJava側でExceptionを引き起こさない</td></tr>
<tr><td>296</td><td>"intent"</td><td><code>Bundle</code>内の「3番目」のキー。実際には最初のキーを上書きする:<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=651-659;drc=584140c83a456b5de99880b440c2d5dfc3c70506">"intent"は以前に見られたキーよりも小さい<code>hashCode()</code>を持つため、<code>ArrayMap.append()</code>メソッドは値の置換を許可する<code>put()</code>を使用する</a>。そうでなければ、<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/ArrayMap.java;l=667-675;drc=584140c83a456b5de99880b440c2d5dfc3c70506">後で<code>validate()</code>によって拒否される重複キーが発生する</a></td></tr>
<tr><td>316</td><td>4</td><td><code>VAL_PARCELABLE</code>。ここから実際に起動される<code>Intent</code>を含む<code>LazyValue</code>が始まる</td></tr>
<tr><td>536</td><td>"1&y~pw"</td><td>書き込まれたが、3つのキーと値のペアがすべて既に読み取られたため読み取られないパディング項目。<a href="https://cs.android.com/android/_/android/platform/system/tools/aidl/+/96a02f50fdfa4d20aa46ae2dde927257eac46d4a">AIDLインターフェースとは異なり</a>、<code>Bundle</code>に対して<code>enforceNoDataAvail()</code>チェックは行われない(ただし、あったとしても、<a href="https://github.com/michalbednarski/ReparcelBug2/issues/3">期待される長さを指定するダミーエントリを挿入することでバイパスできる</a>)</td></tr>
</table>

# どのように2回発生したか

次に、4つのパッチについて説明します。そのうち2つはこのライトアップが対象とする脆弱性を修正するものです。

* CVE-2023-20944([速報](https://source.android.com/docs/security/bulletin/2023-02-01#framework)、[パッチ](https://android.googlesource.com/platform/frameworks/base/+/d0bc9026e2e62e09fa88c1bcbf1dc1c3fb001375%5E%21/)):これは私が見つけた別の脆弱性です。これと同様に、パッチはどのように悪用されるかを明確にしていませんが、<a href="https://konata.github.io/posts/creator-mismatch/">他の誰かがそれを解明したようです(中国語のブログ記事)</a>
* CVE-2023-21098([速報](https://source.android.com/docs/security/bulletin/2023-04-01#framework)、[パッチ](https://android.googlesource.com/platform/frameworks/base/+/107e6377328486fca55131ea06ca9d6a3c1585e0%5E%21/)):これはここで提示したエクスプロイトを初めて報告したときのものです。そのパッチはまた、Android 13より前のバージョンに適用可能な`checkKeyIntentParceledCorrectly()`バイパスの修正も導入しています
* CVE-2023-35669([速報](https://source.android.com/docs/security/bulletin/2023-09-01#framework)、[パッチ](https://android.googlesource.com/platform/frameworks/base/+/f810d81839af38ee121c446105ca67cb12992fc6%5E%21/)):これは私の報告への対応ではありませんが、CVE-2023-20944が対象としていたのと同じ問題を、`AccountManager.KEY_INTENT`が`ChooseTypeAndAccountActivity`以外のActivityによって起動されるケース(例えば、<a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java;l=95-107;drc=32813a2bef49b172aed89122b4eb50bf14026ddc">`AddAccountSettings`</a>。これは最初にバグを報告したときに見逃していたものです)に対して修正するために作られたものだと思います。この変更により、型付きの`bundle.getParcelable()`の使用が型なしのものと手動の`getClass() != Intent.class`チェックに置き換えられ、実際にはCVE-2023-21098の修正が元に戻されました
* CVE-2023-45777([速報](https://source.android.com/docs/security/bulletin/2023-12-01#framework)、[パッチ](https://android.googlesource.com/platform/frameworks/base/+/f4644b55d36a549710ba35b6fb797ba744807da6%5E%21/)):これはこのエクスプロイトを2回目に報告したときのものです。パッチは手動の`getClass() != Intent.class`チェックを維持しましたが、それに加えて型付きの`bundle.getParcelable()`の使用を復活させました。これは両方の問題を修正する良い方法です。

同じエクスプロイトがCVE-2023-21098とCVE-2023-45777の両方で機能しますが、`checkKeyIntentParceledCorrectly()`をバイパスする方法は異なります。

CVE-2023-21098の場合、<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=3519-3521;drc=cdd30b5c040ba7ebd0a1cc6009183ff602434fc0">チェックされた`Bundle`に`Intent`がなかった場合、`checkKeyIntent()`は実際には呼び出されませんでした</a>。`checkKeyIntent()`が`checkKeyIntentParceledCorrectly()`を呼び出すものであるため、元の`Bundle`に`Intent`が含まれていないように見える場合、再シリアライズ後の`Bundle`はチェックされませんでした。

CVE-2023-45777の場合、`checkKeyIntentParceledCorrectly()`は正しく呼び出されましたが、<a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4921-4929;drc=b0f6558fb36eb76df35c516ec5a65030a34a8734">型引数なしの`getParcelable()`呼び出しの前にそこで`writeBundle()`が発生しました(その時点まで`Bundle`はその内容を変更していませんでした)</a>。
ツールをダウンロード