DevTools経由のChromeサンドボックス脱出事件
(ading.dev)- ChromiumのWebUI権限境界、エンタープライズポリシーのテスト機能、DevTools拡張APIの欠陥がつながり、悪意あるChrome拡張がわずかなユーザー操作だけでシェルコマンド実行にまで到達できた
- 攻撃経路は、
chrome://policyで文書化されていないポリシーテストAPIを呼び出してユーザーポリシーを変更し、Browser Switcherの代替ブラウザパスと引数をシェルコマンドとして悪用するものだった chrome.devtools.inspectedWindow.reload()はinjectedScriptの実行を許可しており、WebUIへ遷移する時点でのアクセス遮断の遅延や、クラッシュ後に残ったPage.reloadリクエストにより、privileged WebUIでコードが実行され得た- Googleは脆弱性をP1/S1に分類し、
Page.reloadのloaderId検証、inspectedWindow.reload()のURL検査、WebUIハンドラーでのポリシーテスト有効化チェックを追加した - 関連する脆弱性にはCVE-2024-5836とCVE-2024-6778が割り当てられ、いずれもCVSS 8.8 Highとなり、最終的な報奨金は**$20,000**だった
Chromium WebUIとサンドボックス境界
- Chromiumは信頼できないコードをサンドボックス内で実行し、Chrome拡張のJavaScriptも付与された権限とアクセス可能なAPIの範囲内でのみ動作するべきものとされている
- 拡張機能の権限だけでもログイン情報やブラウザ履歴を盗むことはできるが、原則として影響範囲はブラウザ内部にとどまるべきである
- ChromiumのGUIの一部は、
chrome://settings、chrome://historyのようなWebUIとして実装されている- WebUIはHTML、CSS、JavaScriptで書かれているが、ブラウザ内部の情報を表示・変更する必要があるため、通常のWebページより高い権限を持つ
- WebUIフロントエンドのJavaScriptは、非公開APIを通じてブラウザのネイティブC++コードと通信できる
- WebUIでコード実行が可能になるとChromiumサンドボックスの回避につながるため、攻撃者が
chrome://ページで信頼できないJavaScriptを実行できないようにすることが重要である - たとえば
chrome://downloadsで.exeのダウンロード項目をクリックすると実行ファイルが開かれる可能性があるため、Chromiumはファイルを開く動作が実際のユーザー入力に由来するものかを検査している
エンタープライズポリシーテスト機能の回避
- 脆弱性の探索はChromiumのenterprise policy systemから始まった
- このシステムは、企業や学校が所有する端末に管理者が特定の設定を強制適用するための機能である
- ポリシーは通常Googleアカウントに紐づき、Googleの管理サーバーからダウンロードされる
- ポリシーはdevice policiesとuser policiesに分かれる
- device policiesはChrome OS端末全体の設定を管理する
- user policiesは特定のユーザーまたはブラウザインスタンスに適用され、すべてのプラットフォームで利用できる
- Linuxでは
/etc/opt/chrome/policiesにJSONファイルを置く方式でGoogle Chromeインスタンスにuser policiesを適用できるが、このディレクトリに書き込むにはroot権限が必要である
- 現在の端末に適用されたポリシーは
chrome://policyWebUIで確認できる- このページは適用済みポリシーの一覧、ポリシーサービスのログ、JSONエクスポート機能を提供する
- 通常、このページでポリシーを編集する方法はない
- Chrome v117のChrome Enterpriseリリースノートには、**
chrome://policy/test**ページがBeta、Dev、Canaryチャネルでポリシーテストを許可するという項目があった- この機能はChromiumの文書では、そのリリースノート以外では言及されていなかった
- 正常に有効化するには、文書化されていない
PolicyTestPageEnabledポリシーが必要である - このポリシーがない場合、
chrome://policy/testはchrome://policyへリダイレクトされる
setLocalTestPoliciesの検証失敗
chrome://policy/testのJavaScriptコードは、sendWithPromise('setLocalTestPolicies', ...)を使ってテストポリシーを設定するsendWithPromise()はWebUI非公開APIであるchrome.send()のラッパーである- この呼び出しはC++ハンドラー関数にリクエストを送り、ハンドラーはブラウザ内部の処理を実行できる
chrome://policyコンソールからsetLocalTestPoliciesを直接呼び出すと、最初はブラウザがクラッシュし、ログにはポリシー配列が必要だというメッセージが残った- ポリシー配列の形式を合わせて
AllowDinosaurEasterEggのようなuser policyを渡すと、機能を明示的に有効化していないにもかかわらず任意のポリシーが設定された - C++側の
HandleSetLocalTestPoliciesハンドラーはlocal_test_providerの存在だけを確認しており、ポリシーテスト機能が実際に許可されているかは検査していなかった LocalTestPolicyProvider::CreateIfAllowed()はIsPolicyTestingEnabled(nullptr, channel)を呼び出す- 第1引数の
pref_serviceがnullだったため、PolicyTestPageEnabledの確認がスキップされた - 残る検査はrelease channelが
CANARYまたはDEFAULTかどうかだけだった
- 第1引数の
- unbranded Chromiumビルドでは
GOOGLE_CHROME_BRANDINGコードがコンパイルされないため、チャネルはUNKNOWNのままになる- enumでは
UNKNOWN = 0、DEFAULT = UNKNOWNであるため、Chromiumおよび派生ビルドではチャネル検査を通過する - branded Google Chrome stableビルドではrelease channelが正しく設定されるため、このバグはstable版Google Chromeでは動作しない
- enumでは
Browser Switcherにつながったシェルコマンド実行
- 任意のuser policy設定が可能になったことで、ChromeエンタープライズポリシーのうちLegacy Browser Supportモジュールがサンドボックス脱出経路となった
- Legacy Browser SupportはBrowser Switcherとも呼ばれ、ユーザーがChromiumで特定のURLにアクセスすると代替ブラウザを起動するように設計されている
- Internet Explorerユーザーのサポートのために作られた機能である
- 動作はポリシーで制御される
AlternativeBrowserPathとAlternativeBrowserParametersポリシーを組み合わせると、Chromiumに「代替ブラウザ」として任意のシェルコマンドを実行させることができる- これらのBrowser SwitcherポリシーはLinux、macOS、Windowsにのみ存在する
- 例の流れは次のとおり
BrowserSwitcherEnabledをtrueに設定するBrowserSwitcherUrlListにexample.comを入れるAlternativeBrowserPathをLinuxでは/bin/bashに設定するAlternativeBrowserParametersを["-c", "xcalc # ${url}"]のように設定する
- ブラウザが
example.comへ移動するとBrowser Switcherが作動し、/bin/bash -c 'xcalc # https://example.com'の形式のコマンドが実行される${url}の置換値は#の後ろに配置し、シェルコメントとして扱わせる
chrome://policyでポリシーを設定した後、window.open("https://example.com")を呼び出すと、JavaScriptだけで任意のシェルコマンド実行にまでつながる
DevTools拡張APIの回避経路
- 前段階だけでは、被害者が
chrome://policyで悪意あるコードをブラウザコンソールに貼り付ける必要があるため、実用性は低かった - 自動実行経路は悪意あるChrome拡張を通じて見つかった
- 拡張機能はページにJavaScriptを挿入できるが、privileged WebUIページではJavaScriptを実行できないべきである
- 拡張機能がページでJavaScriptを実行する主なAPIは4つあった
chrome.scripting- Manifest v2の
chrome.tabs chrome.debuggerchrome.devtools.inspectedWindow
- 調査対象は、相対的に強化が不十分だと見られた
chrome.devtools.inspectedWindowだったchrome.devtoolsAPIを使う拡張は、manifestにdevtools_pageフィールドを持つ必要がある- ユーザーがDevToolsを開くとそのページがiframeとして読み込まれ、その中で
chrome.devtoolsAPIを利用できる
- 既存のDavid Ercegのバグ報告には、
chrome.devtools.inspectedWindow.eval()を使ってWebUIでコード実行に到達した事例があった- 通常は検査対象ページがWebUIへ移動するとDevTools APIの使用が無効化されるべきである
- 回避の核心は、ChromeがAPIを無効化する前にevalリクエストを送り、そのリクエストをWebUIページに到達させることだった
inspectedWindow.reload()とabout:blankの特殊性
chrome.devtools.inspectedWindow.reload()もinjectedScript引数を受け取ると、検査対象ページでJavaScriptを実行できる- WebUIが開いた
about:blankページでinspectedWindow.reload()を呼び出すと、privileged pageでJavaScriptを実行できたabout:blankはURL自体は特別ではないが、自身を開いたページの権限とoriginを継承するchrome://settingsが開いたabout:blankは、originがchrome://settingsであるprivileged pageだった
- DevTools APIの無効化コードは、検査対象のURLだけを確認し、originは確認していなかった
- URLが普通に見えても、originはprivileged originである可能性がある
about:blank経路だけでは、chrome://policyがabout:blankポップアップを開かないため、exploit chainにすぐ使うのは難しかった- ただし
inspectedWindow.eval()が失敗する状況でも、inspectedWindow.reload()はchrome://settingsでJavaScriptを実行したeval()にはorigin検査に相当する独自の検査があったが、reload()には同じレベルの検査がなかったことを示している
race conditionからクラッシュベース方式への安定化
- 最初のexploit chainは
inspectedWindow.reload()呼び出しを繰り返し、検査対象ページがWebUIへ移動した直後、DevToolsページがAPIを無効化する前の短い時間窓を狙った- 検査対象ページとDevToolsページが別プロセスであることが前提だった
chrome://policyへ移動する瞬間とDevTools API無効化の間にreload()リクエストが入ると、WebUIでコードが実行される
- この方式は動作したが、信頼性は低かった
- 調整後で約**70%**程度成功した
- 深刻な脆弱性ではあるが、不安定さはseverityを下げ得るものだった
- その後、David Ercegの以前のアプローチのように、タブクラッシュ後にデバッガーリクエストが残る動作を
inspectedWindow.reload()にも適用できるかを試した debugger文を2回連続でトリガーするとタブがクラッシュし、Page.reloadリクエストがキューに残ってWebUIへ移動した後に実行され得た- この方式ではrace conditionが不要になり、100% reliableに動作した
- 以前のバグ修正でGoogleはクラッシュ後のpending debugger requestsを消去するよう修正したが、
Page.reloadリクエストは例外として残していたinspectedWindow.reload()は内部的にPage.reloadリクエストを送るため、この例外の影響を受ける- 当時のパッチは、
Page.reloadがスクリプトを実行できる点を防げていなかった
- タブクラッシュは
debugger方式以外にもメモリ不足を誘発する方法で可能だったが、最終PoCではより高速なdebuggerクラッシュが使われた
最終exploit chainとユーザー操作
- 最終PoCは次の順序で動作する
chrome.devtools.inspectedWindow.reload()脆弱性を使ってchrome://policyでJavaScript payloadを実行する- payloadが
sendWithPromise("setLocalTestPolicies", policy)を呼び出してユーザーポリシーを設定する BrowserSwitcherEnabled、BrowserSwitcherUrlList、AlternativeBrowserPath、AlternativeBrowserParametersを設定するwindow.open()またはページ遷移でBrowser Switcherをトリガーし、シェルコマンドを実行する
- PoCはOS別の電卓起動コマンドを使う
- Windows:
C:\Windows\System32\cmd.exeとcalc.exe - Linux:
/bin/bashとxcalc - macOS:
/bin/bashとopen -na Calculator
- Windows:
- ユーザー操作はDevToolsを開かせる程度だった
- 例の画面の「extension install error」は、ユーザーにDevToolsを開かせるための仕掛けだった
- DevToolsが開かれると、sandbox escapeにつながるchainが開始される
Googleの修正とCVE割り当て
- Googleは報告後、脆弱性を迅速に確認し、P1/S1に分類した
- P1/S1はhigh priorityとhigh severityを意味する
- その後数週間にわたり、3つの主要な修正が適用された
Page.reloadコマンドにloaderId引数を追加およびrenderer側のloaderID検査- コマンドが単一のoriginにのみ有効で、意図せずprivileged pageに到達しても動作しないようにした
inspectedWindow.reload()関数でのURL検査- 拡張APIアクセスの取り消しだけに依存しないようになった
- WebUIハンドラーでテストポリシーの有効化有無を検査
- テストポリシーが完全にブロックされるようにした
- race condition関連の脆弱性にはCVE-2024-5836が割り当てられた
- CVSS severity scoreは8.8 Highである
- 検査対象ページのクラッシュ関連の脆弱性にはCVE-2024-6778が割り当てられた
- この脆弱性もCVSS severity score 8.8を受けた
- 修正がリリースブランチにマージされた後、Chrome VRPパネルが報奨を決定し、最終的な報奨金は**$20,000**だった
公開スケジュールと資料
- タイムラインは次のとおり
- 4月16日: test policiesバグを発見
- 4月29日:
inspectedWindow.reload()race conditionバグを発見 - 5月1日: Googleにバグを報告
- 5月4日: GoogleがP1/S1に分類
- 5月5日: 検査対象ページのクラッシュ関連バグを発見し、報告を更新
- 5月6日: Googleがchainの各部分について別個のバグ報告を要請
- 7月8日: バグ報告がfixedとして表示
- 7月13日: 報奨決定のためChrome VRPパネルへ転送
- 7月17日: VRPパネルが報奨金**$20,000**を決定
- 10月15日: 全体のバグ報告が公開
- 関連する元のバグ報告はcrbug.com/338248595で確認できる
- 各脆弱性部分のPoCはGitHubリポジトリで公開されている
inspectedWindow.reloadバグはChrome v45までさかのぼって動作していた- 文書化されておらず、完成度が低く、安全でない機能をすべてのユーザーに配布すると、単純なミスが組み合わさって重大度の高い脆弱性につながり得る
1件のコメント
Hacker News のコメント
ページ URL が
${url}に置換され、コマンドを壊さないよう#の後ろに置けばコメントになるとのことだったが、このポリシーには URL がAlternativeBrowserParametersのどこかへ渡されなければならない という検証ロジックのようなものはあるのだろうか?プログラミング、Web 開発、サイバーセキュリティに関心のある 高校生 だなんて本当に印象的
責任ある公開手続きまで守る職業倫理も素晴らしく、将来大成しそうな人に見える
素晴らしい記事と作業で、発見が続くにつれて興奮が高まっていく過程を一緒に追っているように感じた
報奨金も十分に受け取るに値する
脆弱性をつなげていく過程もきれいで、文章も素晴らしい。脆弱なコードがどのように動作するのかを分解して見せてくれた点もよかった
「もう一度試すには F12 を押してください」のような単純なトリックは、見るたびに感心してしまうし、本当に茶目っ気たっぷりだ
以前、同じ API で Chrome OS の
croshシェルをデバッグし、OS の保護を迂回して開発者向けデバイスで root アクセスまで得たことを思い出す。CVE-2014-3172 だったただし、この記事の筆者ははるかに難しい障害を迂回しなければならず、本当に見事な仕事だ
夜もかなり遅いので WebUI 検証で何が壊れていたのか深掘りするのは難しいが、最後まで掘り下げて突き止めた点がよい
私たちが配布するもののツールチェーンを疑い、不信感を持つのはかなり標準的な姿勢だが、一方で Google や Microsoft のような大企業の魔法のように便利な開発ツールはあまりにも信用しすぎている。結局、Chromium や VSCode の中に何が隠れているのかを心配するより、自分のコードを書いてテストしたいからだ
私が読んだ中でも指折りの素晴らしい記事だ
本当に巧妙な 追跡作業 だった
ブラウザのコードを掘り下げてここまで見つけ出した努力はすごいし、記事も非常に興味深く詳細だ
高校生だなんて、うわ、本当にすごい
Chromium プロジェクトは
chrome://net-internalsが複雑すぎるという理由で削除しておきながら、半完成の JSON 編集サポートが付いたchrome://policyを追加した