
SnatchBox (CVE-2020-27935) は、macOS バージョン 10.15.x までに影響するサンドボックスエスケープの脆弱性およびエクスプロイトです。
SnatchBox(CVE-2020-27935)は、macOS 10.15 までと、macOS 11.0 の初期ベータ版に影響を与えるサンドボックスエスケープの脆弱性です。SnatchBox の最も重大な影響は、悪意のある公開者が必須の macOS App Store サンドボックスを脱出し、ユーザーのすべてのファイルへの完全なアクセスを取得できることです。これにより、macOS 上の App Store のセキュリティモデルが破壊されます。
macOS では、例えば iOS とは対照的に、ユーザースペースのタスクが自発的にサンドボックスに入るという事実自体が、設計上脆弱です。潜在的悪意のある作成者がそのメモリマッピングとコンテンツをほぼ完全に制御できるタスクであり、サンドボックス初期化の前に実行されるコード(dyld 自体や Objective-C ランタイムなど)がこの潜在的に悪意のあるバイナリのコンテンツを解析するため、慎重に細工されたデータを使用して、サンドボックス初期化の前にコード実行を獲得できます。もしプロセスがサンドボックスに強制される前に dyld コードを含むいかなるコードも実行しないのであれば、その場合、早期のコード実行は攻撃者に何ももたらさないため、問題にはならなかったでしょう。これは概念的に Saagar Jha氏のサンドボックスバイパス に類似していますが、新たに導入された緩和策と App Store の検証を回避する点が異なります。
macOS 10.15 より前では、このバグの悪用は非常に簡単です。サンドボックス初期化の前に使用されるクラス(OS_xpc_object など)の Objective-C カテゴリを含むバイナリを作成し、サンドボックス初期化の前に使用されるメソッド(+initialize など。クラスへの最初のアクセス時に暗黙的に呼び出されます)を(できればランタイム警告を避けるために継承されたメソッドを)オーバーライドします。カテゴリはサンドボックス初期化の前にロードされ、OS_xpc_object(または他の適切な被害者クラス)への最初のアクセスはカテゴリのロード後かつサンドボックス初期化前に行われるため、攻撃者が提供した +initialize メソッド(または他の適切な被害者メソッド)がサンドボックス初期化の前に呼び出され、例えばコンテナ外のデータにアクセスできるようになります。あるいは、攻撃者は _libsecinit_initializer(サンドボックスを初期化するもの)への参照を nop 相当の関数に置き換えて、実行を再開した後でも(潜在的に条件付きで)サンドボックスを無効化できます。
macOS 10.15 で使用される Objective-C ランタイムは、前述の悪用手法に対して脆弱ではありません。カテゴリが didCallDyldNotifyRegister が設定される前にロードされないため、私たちの +initialize メソッドはサンドボックス初期化が発生した後にのみ呼び出されるようになります。
しかし、map_images は依然として私たちのバイナリに対して呼び出されるため、ランタイムデータを意図しない方法で変更し、サンドボックス初期化の前にコードを実行できるようにすることが可能です。完全なコメント付きの悪用コードは main.c にありますが、ここでは基本的な詳細を説明します。data ポインタが libxbc.dylib 内の場所を指す Objective-C クラス構造を細工します。その場所は、flags のビット 31(RW_REALIZED)が設定されるように選択する必要があります。これにより、ランタイムがこの無効なクラスを realize してクラッシュすることを試みず、firstSubclass はオーバーライドしたいクラスの isa とアドレスを共有する必要があります。別の(メタ)クラスがこの無効なクラスから継承し、独自の +initialize メソッドを提供します。このサブクラスを __objc_nlclslist に追加して、ランタイムがこのクラスを realize するようにします。
ランタイムが私たちのサブクラスを realize するとき(これはサンドボックス初期化の前に発生します)、無効なスーパークラスとサブクラスに対して addSubclass を呼び出し、被害者の isa を私たちのサブクラスへのポインタで置き換え、事実上そのすべてのメソッドを私たちの +initialize に置き換えます。適切な被害者クラスを選択した場合、私たちの +initialize メソッドはサンドボックス初期化の前に呼び出されるため、再び _libsecinit_initializer への参照を nop に置き換え(条件付きかどうかは問わない)、後でクラッシュせずに実行を再開できるように、行ったランタイムの変更を修正できます。
提供されるデモは、make を実行し、~/Documents/SecretDocument.txt にファイルを作成し、ターミナルから SnatchBox.app/Contents/MacOS/SnatchBox を実行することでビルドできます(com.apple.security.app-sandbox によってバンドルが必要になるため作成されますが、これは依然としてコマンドラインプログラムです)。ビルドされたバイナリは com.apple.security.app-sandbox で署名されています。通常は ~/Documents/SecretDocument.txt へのアクセスを防ぎますが(コンテナ内にないため)、そのデータを読み取ることができます。ランタイム構造の変更により、このデモは macOS 10.14 以前ではそのままでは動作せず、10.15 と 11.0 では動作します(テスト済み: 10.15.4、10.15.7、11.0 Beta (20A5354i))。2 つの悪用手法を組み合わせて両方のランタイムバージョンをターゲットにできますが、そのようなデモは提供されていません。
実行例:
CatalinaVM:SnatchBox lior$ make
mkdir -p SnatchBox.app/Contents/MacOS/
clang -O3 -Wall -framework Foundation main.m -o SnatchBox.app/Contents/MacOS/SnatchBox
cp Info.plist SnatchBox.app/Contents/
codesign --force --sign - SnatchBox.app --entitlements ent.xml
CatalinaVM:SnatchBox lior$ echo "Quack"> ~/Documents/SecretDocument.txt
CatalinaVM:SnatchBox lior$ codesign -d --entitlements :- SnatchBox.app/Contents/MacOS/SnatchBox
Executable=/Volumes/SharedFolders/Home/Projects/SnatchBox/SnatchBox.app/Contents/MacOS/SnatchBox
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
CatalinaVM:SnatchBox lior$ SnatchBox.app/Contents/MacOS/SnatchBox
Found libsecinit_initializer at 0x7fff72309124
Found libSystem.B.dylib at 0x7fff6f0de000
Found __DATA at 0x7fff984eeca0
Replacing libsecinit_initializer reference at 0x7fff984eed48 with a nop
2020-12-18 16:31:48.196 SnatchBox[804:8043] Attempting to read protected file: /Users/lior/Documents/SecretDocument.txt
2020-12-18 16:31:48.197 SnatchBox[804:8043] Escaped sandbox! The contents are: <51756163 6b0a>
前述のとおり、これにより App Store ポリシーでサンドボックスでの実行が必須であるにもかかわらず、サンドボックス内で実行されない macOS App Store アプリケーションを作成できます。この脆弱性はフレームワークでも使用でき、それ以外は正規の App Store アプリケーションが使用する可能性があります。最後に、これは "Xcode Ghost" に類似したものと組み合わせて、サンドボックス外で実行される悪質なコードを App Store アプリケーションに大量に注入することもできます。
Apple は macOS 11.0 のベータテスト段階で、realizeClassWithoutSwift に malloc_size への呼び出しを追加することでこの悪用を修正しました。これにより、クラスが(私たちの偽のクラスのように RW_REALIZED として)realized とマークされている場合、実際に正しいサイズ(0x20 バイト)の有効な malloc された data ポインタを持つことが確認されます。そうでない場合、ランタイムは realized class 0x100002078 has corrupt data pointer 0x7fff88c00948 に類似したメッセージで中止します。この修正は、直接影響を受けなかったとしても、iOS、iPadOS、tvOS、watchOS にも適用されました。