ウェブ デベロッパー向けのサイト分離

デスクトップ版 Chrome 67 には、サイト分離という新しい機能がデフォルトで有効になっています。この記事では、サイト分離の概要、必要性、ウェブ デベロッパーが注意すべき理由について説明します。

サイト分離とは何ですか?

インターネットは、猫の動画の視聴や暗号通貨ウォレットの管理などのために使用しますが、fluffycats.example に貴重な暗号通貨にアクセスしてほしくありません。幸いなことに、同一オリジン ポリシーにより、ウェブサイトは通常、ブラウザ内で互いのデータにアクセスできません。それでも、悪意のあるウェブサイトがこのポリシーを回避して他のウェブサイトを攻撃しようとすることがあります。また、同一生成元ポリシーを適用するブラウザ コードにセキュリティ バグが見つかることもあります。Chrome チームは、このようなバグをできるだけ早く修正することを目指しています。

サイト分離は、このような攻撃が成功しにくいように、追加の防御ラインを提供する Chrome のセキュリティ機能です。各ウェブサイトのページが常に別々のプロセスで処理されるようになり、各プロセスは、実行可能な処理が制限されるサンドボックス内で実行されます。また、プロセスが他のサイトから特定のタイプのセンシティブ データを受信しないようにします。その結果、サイト分離を使用すると、悪意のあるウェブサイトが Spectre のような投機的なサイドチャネル攻撃を使用して他のサイトからデータを盗むことがはるかに難しくなります。Chrome チームが追加の適用を完了すると、攻撃者のページが独自のプロセスで一部のルールを破った場合でも、サイト分離が役立ちます。

サイト分離により、信頼できないウェブサイトが他のウェブサイトのアカウントから情報にアクセスしたり、情報を盗んだりするのが実質的に難しくなります。最新の Meltdown や Spectre サイドチャネル攻撃など、さまざまな種類のセキュリティ バグに対する保護を強化します。

サイト分離について詳しくは、Google セキュリティ ブログの記事をご覧ください。

Cross-Origin Read Blocking

すべてのクロスサイト ページが個別のプロセスに配置されている場合でも、ページは画像や JavaScript などの一部のクロスサイト サブリソースを正当にリクエストできます。悪意のあるウェブページは、<img> 要素を使用して、銀行残高などの機密データを含む JSON ファイルを読み込む可能性があります。

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

サイト分離がないと、JSON ファイルの内容がレンダラ プロセスのメモリに保存されます。この時点で、レンダラは有効な画像形式ではないことを認識し、画像をレンダリングしません。しかし、攻撃者は Spectre などの脆弱性を悪用して、そのメモリ チャンクを読み取る可能性があります。

攻撃者は、<img> ではなく <script> を使用して機密データをメモリに commit することもできます。

<script src="https://your-bank.example/balance.json"></script>

Cross-Origin Read Blocking(CORB)は、MIME タイプに基づいて balance.json の内容がレンダラ プロセスのメモリに侵入するのを防ぐ新しいセキュリティ機能です。

CORB の仕組みを詳しく見ていきましょう。ウェブサイトは、サーバーから次の 2 種類のリソースをリクエストできます。

  1. HTML、XML、JSON ドキュメントなどのデータリソース
  2. 画像、JavaScript、CSS、フォントなどのメディア リソース

ウェブサイトは、Access-Control-Allow-Origin: * などの許可付き CORS ヘッダーを使用して、自身のオリジンまたは他のオリジンからデータリソースを受け取ることができます。一方、メディア リソースは、制限が緩やかな CORS ヘッダーがなくても、任意の送信元から含めることができます。

CORB は、次の場合に、レンダラ プロセスがクロスオリジン データリソース(HTML、XML、JSON など)を受信しないようにします。

  • リソースに X-Content-Type-Options: nosniff ヘッダーがある
  • CORS でリソースへのアクセスが明示的に許可されていない

クロスオリジン データリソースに X-Content-Type-Options: nosniff ヘッダーが設定されていない場合、CORB はレスポンスの本文をスニッフィングして、HTML、XML、JSON のいずれであるかを判断しようとします。これは、一部のウェブサーバーが構成ミスにより、画像を text/html として提供することがあるためです。

CORB ポリシーによってブロックされたデータリソースは、空としてプロセスに提示されますが、リクエストはバックグラウンドで引き続き行われます。その結果、悪意のあるウェブページがクロスサイト データをプロセスに引き込んで盗み出すのは困難になります。

セキュリティを最適化し、CORB のメリットを活用するには、次のことをおすすめします。

  • レスポンスに正しい Content-Type ヘッダーを付けてください。(たとえば、HTML リソースは text/htmlJSON MIME タイプの JSON リソース、XML MIME タイプの XML リソースとして提供する必要があります)。
  • X-Content-Type-Options: nosniff ヘッダーを使用して、スニッフィングをオプトアウトします。このヘッダーがない場合、Chrome はコンテンツをすばやく分析してタイプが正しいことを確認しようとしますが、JavaScript ファイルなどのブロックを回避するためにレスポンスの許可側にエラーが発生するため、自分で正しいことを行うことをおすすめします。

詳しくは、ウェブ デベロッパー向けの CORB に関する記事またはCORB の詳細な説明をご覧ください。

ウェブ デベロッパーがサイト分離に注意を払うべき理由

ほとんどの場合、サイト分離は、ウェブ デベロッパーに直接公開されない、ブラウザの裏側の機能です。たとえば、新しいウェブ公開 API はありません。一般に、ウェブページは、サイト分離ありの場合とサイト分離なしの場合で違いを認識できません。

ただし、このルールには例外があります。サイト分離を有効にすると、ウェブサイトに影響する可能性のある微妙な副作用がいくつか発生します。Google はサイト分離に関する既知の問題のリストを維持しています。以下では、最も重要な問題について詳しく説明します。

全ページレイアウトが非同期になりました

サイト分離では、ページのフレームが複数のプロセスに分散される可能性があるため、全ページレイアウトが同期されることが保証されなくなりました。レイアウトの変更がページ上のすべてのフレームにすぐに伝播すると想定しているページに影響する可能性があります。

たとえば、social-widget.example でホストされているソーシャル ウィジェットと通信する fluffykittens.example という名前のウェブサイトについて考えてみましょう。

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

最初は、ソーシャル ウィジェットの <iframe> の幅は 123 ピクセルです。その後、FluffyKittens ページで幅が 456 ピクセルに変更され(レイアウトがトリガーされます)、ソーシャル ウィジェットにメッセージが送信されます。このウィジェットのコードは次のとおりです。

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

ソーシャル ウィジェットが postMessage API を介してメッセージを受信するたびに、ルート <html> 要素の幅がログに記録されます。

どの幅の値がログに記録されますか?Chrome でサイト分離を有効にする前は、正解は「456」でした。document.documentElement.clientWidth にアクセスするとレイアウトが強制されます。これは、Chrome でサイト分離が有効になる前は同期的でした。ただし、サイト分離を有効にすると、クロスオリジンのソーシャル ウィジェットの再レイアウトが、別のプロセスで非同期的に行われるようになりました。そのため、回答は 123(古い width 値)にもできます。

ページがクロスオリジン <iframe> のサイズを変更してから postMessage を送信する場合、サイト分離では、受信フレームがメッセージを受信したときに新しいサイズをまだ認識していない可能性があります。より一般的には、レイアウト変更がページ上のすべてのフレームにすぐに伝播すると想定すると、ページが破損する可能性があります。

この特定の例では、より堅牢なソリューションとして、親フレームに width を設定し、resize イベントをリッスンすることで <iframe> の変化を検出します。

アンロード ハンドラが頻繁にタイムアウトする

フレームが移動または閉じると、古いドキュメントとそれに埋め込まれたサブフレーム ドキュメントがすべて unload ハンドラを実行します。新しいナビゲーションが同じレンダラ プロセスで発生する場合(同じオリジンのナビゲーションの場合など)、古いドキュメントとそのサブフレームの unload ハンドラは、新しいナビゲーションを commit する前に任意の長い時間実行される可能性があります。

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

この状況では、すべてのフレームの unload ハンドラの信頼性が非常に高くなります。

ただし、サイト分離を使用しなくても、一部のメインフレーム ナビゲーションはクロスプロセスであり、アンロード ハンドラの動作に影響します。たとえば、アドレスバーに URL を入力して old.example から new.example に移動すると、new.example ナビゲーションは新しいプロセスで発生します。old.example とそのサブフレームのアンロード ハンドラは、new.example ページが表示された後、バックグラウンドで old.example プロセスで実行されます。古いアンロード ハンドラは、特定のタイムアウト内に完了しなかった場合に終了します。アンロード ハンドラはタイムアウト前に完了しない可能性があるため、アンロード動作の信頼性は低くなります。

サイト分離では、すべてのクロスサイト ナビゲーションがクロスプロセスになるため、異なるサイトのドキュメントがプロセスを共有することはありません。その結果、上記の状況が適用されるケースが増え、<iframe> のアンロード ハンドラは多くの場合、上記のバックグラウンド動作とタイムアウト動作になります。

サイト分離によるもう 1 つの違いは、アンロード ハンドラの新しい並列順序です。サイト分離がないと、アンロード ハンドラはフレーム全体で厳密なトップダウン順序で実行されます。ただし、サイト分離では、アンロード ハンドラは異なるプロセス間で並行して実行されます。

これらは、サイト分離を有効にした場合の基本的な結果です。Chrome チームは、可能な限り、一般的なユースケースでのアンロード ハンドラの信頼性を向上させる取り組みを進めています。また、サブフレーム アンロード ハンドラが特定の機能をまだ利用できないバグがあることも認識しており、解決に向けて取り組んでいます。

アンロード ハンドラの重要なケースとして、セッション終了の ping の送信があります。これは通常、次のように行われます。

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

この変更に照らし合わせてより堅牢なアプローチとしては、代わりに navigator.sendBeacon を使用する方法があります。

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

リクエストをより詳細に制御する必要がある場合は、Fetch API の keepalive オプションを使用できます。

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

まとめ

サイト分離では、各サイトを独自のプロセスに分離することで、信頼できないウェブサイトが他のウェブサイトのアカウントにアクセスしたり、情報を盗み出したりすることを困難にします。その一環として、CORB は機密データ リソースをレンダラ プロセスから除外しようとします。上記の推奨事項に沿って設定することで、これらの新しいセキュリティ機能を最大限に活用できます。

この記事の下書き版を読んでフィードバックをくれた Alex Moshchuk、Charlie Reis、Jason Miller、Nasko Oskov、Philip Walton、Shubhie Panicker、Thomas Steiner に感謝します。