コンテンツ セキュリティ ポリシー

コンテンツ セキュリティ ポリシーによって、最新のブラウザでのクロスサイト スクリプティング攻撃のリスクと影響を大幅に低減できます。

ジョー・メドレー
Joe Medley

ウェブのセキュリティ モデルは同一オリジン ポリシーをベースとしています。https://mybank.com のコードは https://mybank.com のデータにのみアクセスでき、https://evil.example.com にはアクセスは許可されません。各オリジンはウェブの他の部分から分離されており、デベロッパーは構築とプレイのための安全なサンドボックスを提供します。理論的には、これは非常に素晴らしいことです。実際には、攻撃者がシステムを妨害する巧妙な方法を見つけています。

たとえば、クロスサイト スクリプティング(XSS)攻撃は、サイトをだまして悪意のあるコードと一緒に目的のコンテンツを提供させ、同じ送信元ポリシーを回避します。これは大きな問題です。ブラウザは、ページに表示されるすべてのコードを、そのページのセキュリティ オリジンの正当なものとして信頼しているためです。XSS クイック リファレンスは、攻撃者が悪意のあるコードを注入してこの信頼を侵害するために使用する可能性のある、古いものの代表的な手法です。攻撃者が任意のコードの注入に成功すると、ユーザー セッション データが侵害され、秘密にすべき情報が The Bad Guys に流出され、大いにゲームオーバーとなります。当然ながら、可能であればそれを防ぐ必要があります。

この概要では、最新のブラウザでの XSS 攻撃のリスクと影響を大幅に軽減できる防御策である、コンテンツ セキュリティ ポリシー(CSP)について説明します。

概要

  • 許可リストを使用して、許可されるコンテンツと許可されないコンテンツをクライアントに伝えます。
  • 使用できるディレクティブを確認します。
  • 使用されているキーワードを把握します。
  • インライン コードと eval() は有害とみなされます。
  • ポリシーを適用する前に、サーバーにポリシー違反を報告してください。

ソースの許可リスト

XSS 攻撃で悪用される問題は、ブラウザがアプリケーションの一部であるスクリプトと、サードパーティによって不正に挿入されたスクリプトを区別できないことです。たとえば、このページの下部にある Google +1 ボタンを使用すると、このページの提供元のコンテキストで https://apis.google.com/js/plusone.js からコードが読み込まれ、実行されます。そのコードは信頼していますが、apis.google.com のコードが優れていることはブラウザ自体では判断できませんが、apis.evil.example.com のコードはおそらくそうではないでしょう。ブラウザは、ソースに関係なく、ページがリクエストするコードを問題なくダウンロードして実行します。

CSP は、サーバーから配信されるすべてをやみくもに信頼するのではなく、Content-Security-Policy HTTP ヘッダーを定義します。これにより、信頼できるコンテンツのソースの許可リストを作成し、それらのソースからリソースのみを実行またはレンダリングするようブラウザに指示します。攻撃者がスクリプトを挿入するための穴を見つけられたとしても、スクリプトは許可リストと一致せず、実行されません。

Google は apis.google.com を信頼して有効なコードを配信します。また、Google も同じことを信じています。そのため、これら 2 つのソースのいずれかからスクリプトが提供された場合にのみ、スクリプトの実行を許可するポリシーを定義しましょう。

Content-Security-Policy: script-src 'self' https://apis.google.com

実に簡単ですね。ご想像のとおり、script-src は、特定のページに対するスクリプト関連の権限を制御するディレクティブです。ここでは、有効なスクリプト ソースとして 'self' と、別のスクリプトとして https://apis.google.com を指定しています。ブラウザは、apis.google.com から HTTPS を介して、および現在のページの生成元から、JavaScript を忠実にダウンロードして実行します。

コンソールのエラー: コンテンツ セキュリティ ポリシー ディレクティブ script-src 'self' https://apis.google.com に違反しているため、スクリプト「http://evil.example.com/evil.js」の読み込みを拒否されました。

このポリシーを定義すると、ブラウザは他のソースからスクリプトを読み込む代わりに、単にエラーをスローします。巧妙な攻撃者がサイトにコードを注入しようとすると、期待していた成功ではなく、エラー メッセージに直面します。

ポリシーは幅広いリソースに適用される

スクリプト リソースは最も明白なセキュリティ リスクですが、CSP には豊富なポリシー ディレクティブがあり、ページへの読み込みを許可するリソースをかなりきめ細かく制御できます。script-src についてはすでに見てきたので、コンセプトは明確です。

残りのリソース ディレクティブを簡単に見てみましょう。以下のリストは、レベル 2 におけるディレクティブの状態を示しています。レベル 3 の仕様は公開されていますが、主要なブラウザにはほとんど実装されていません

  • base-uri は、ページの <base> 要素に表示できる URL を制限します。
  • child-src は、ワーカーと埋め込みフレーム コンテンツの URL を一覧表示します。たとえば、child-src https://youtube.com と指定すると、YouTube の動画を埋め込むことはできますが、他のオリジンの動画は埋め込めません。
  • connect-src は、(XHR、WebSocket、EventSource を介して)接続できるオリジンを制限します。
  • font-src は、ウェブフォントを提供できるオリジンを指定します。Google のウェブフォントは font-src https://themes.googleusercontent.com を介して有効にできます。
  • form-action は、<form> タグから送信するための有効なエンドポイントをリストします。
  • frame-ancestors は、現在のページを埋め込むことができるソースを指定します。このディレクティブは <frame><iframe><embed><applet> タグに適用されます。このディレクティブは <meta> タグでは使用できません。HTML 以外のリソースにのみ適用されます。
  • frame-src はレベル 2 でサポートが終了しましたが、レベル 3 で復元されています。存在しない場合は、以前と同様に child-src にフォールバックします。
  • img-src は、画像の読み込み元となるオリジンを定義します。
  • media-src は、動画と音声の配信を許可するオリジンを制限します。
  • object-src を使用すると、Flash などのプラグインを制御できます。
  • plugin-types は、ページで呼び出すことができるプラグインの種類を制限します。
  • report-uri は、コンテンツ セキュリティ ポリシーに違反した場合にブラウザからレポートを送信する URL を指定します。このディレクティブは <meta> タグでは使用できません。
  • style-src は、script-src のスタイルシートに相当します。
  • upgrade-insecure-requests は、URL スキームを書き換えて HTTP を HTTPS に変更するようユーザー エージェントに指示します。このディレクティブは、書き換えが必要な古い URL が多数存在するウェブサイトに使用します。
  • worker-src は、ワーカー、共有ワーカー、または Service Worker として読み込むことができる URL を制限する CSP レベル 3 ディレクティブです。2017 年 7 月現在、このディレクティブは実装が制限されています

デフォルトでは、ディレクティブは広く設定されます。ディレクティブ(font-src など)に特定のポリシーを設定しない場合、そのディレクティブは、有効なソースとして * を指定した場合と同様に、デフォルトで動作します(たとえば、フォントをどこからでも制限なく読み込むことができます)。

このデフォルトの動作は、default-src ディレクティブを指定することでオーバーライドできます。このディレクティブは、指定していないほとんどのディレクティブのデフォルトを定義します。一般に、これは -src で終わるすべてのディレクティブに適用されます。default-srchttps://example.com に設定されていて、font-src ディレクティブを指定しなかった場合、https://example.com からフォントを読み込むことはできますが、それ以外の場所からは読み込めません。前の例では script-src のみを指定しました。つまり、画像やフォントなどはどの提供元からでも読み込めます。

次のディレクティブでは、default-src をフォールバックとして使用しません。これらを設定しないと、何も許可したのと同じことになるので注意してください。

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

これらのディレクティブは、特定のアプリケーションに必要な数だけ使用できます。単純に HTTP ヘッダーに各ディレクティブをリストし、ディレクティブをセミコロンで区切ります。特定のタイプの必須リソースをすべて 1 つのディレクティブでリストしていることを確認してください。script-src https://host1.com; script-src https://host2.com のように記述すると、2 番目のディレクティブは無視されます。たとえば、次のように記述すると、両方のオリジンが有効として正しく指定されています。

script-src https://host1.com https://host2.com

たとえば、コンテンツ配信ネットワーク(https://cdn.example.net など)からすべてのリソースを読み込むアプリがあり、フレームで構成されたコンテンツやプラグインは不要なことがわかっている場合、ポリシーは次のようになります。

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

実装の詳細

X-WebKit-CSP ヘッダーと X-Content-Security-Policy ヘッダーは、ウェブ上のさまざまなチュートリアルで使用されています。今後は、これらの接頭辞付きヘッダーを無視する必要があります。最新のブラウザ(IE を除く)は、プレフィックスのない Content-Security-Policy ヘッダーをサポートしています。このヘッダーを使うべきです。

使用するヘッダーに関係なく、ポリシーはページごとに定義されます。保護されるようにするには、すべてのレスポンスとともに HTTP ヘッダーを送信する必要があります。これにより、ニーズに基づいて特定のページのポリシーを微調整できるため、柔軟性が大幅に向上します。たとえば、サイト内で +1 ボタンのあるページとないページがある場合、必要な場合にのみボタンコードが読み込まれるようにすることができます。

各ディレクティブのソースリストは柔軟性があります。ソースはスキーム(data:https:)で指定できます。また、ホスト名のみ(example.com、そのホストの任意のオリジンに一致する example.com、任意のスキーム、任意のポート)から完全修飾 URI(https://example.com:443、HTTPS のみ、example.com のみ、ポート 443 のみと一致)まで指定できます。ワイルドカードは使用できますが、スキーム、ポート、またはホスト名の左端の位置でのみ使用できます。*://*.example.com:* は、example.com 自体ではなく、example.com のすべてのサブドメインに一致し、example.com 自体は一致しません。使用するスキームは問いません。

ソースリストには、次の 4 つのキーワードも指定できます。

  • 予想どおり、'none' は何も一致しません。
  • 'self' は現在のオリジンと一致しますが、そのサブドメインには一致しません。
  • 'unsafe-inline' を使用すると、インラインの JavaScript と CSS を使用できます。(これについては後で詳しく説明します)。
  • 'unsafe-eval' を使用すると、eval のような Text-to JavaScript メカニズムが可能になります。(これについてもお話しします)。

キーワードには一重引用符が必要です。たとえば、script-src 'self'(引用符付き)は現在のホストから JavaScript を実行することを許可します。script-src self(引用符なし)は、現在のホストからではなく「self」という名前のサーバーから JavaScript を許可します(現在のホストからのものは許可しません)。

サンドボックス化

もう一つ注目すべきディレクティブが sandbox あります。これは、これまでに説明した他の手法とは若干異なり、ページが読み込めるリソースではなく、ページが実行できるアクションに制限が設けられます。sandbox ディレクティブが存在する場合、ページは sandbox 属性を持つ <iframe> の内部に読み込まれたものとして扱われます。これにより、ページを一意のオリジンに強制適用したり、フォームの送信を阻止したりするなど、ページにさまざまな影響を及ぼす可能性があります。この記事の範囲外ですが、有効なサンドボックス属性について詳しくは、HTML5 仕様の「サンドボックス化」セクションをご覧ください。

メタタグ

CSP の優先配信メカニズムは HTTP ヘッダーです。ただし、マークアップ内でページのポリシーを直接設定することは便利な場合があります。これを行うには、http-equiv 属性を含む <meta> タグを使用します。

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

frame-ancestorsreport-urisandbox には使用できません。

インライン コードは有害であると考えられる

CSP が許可リストのオリジンに基づいていることは明らかです。これは、特定のリソースセットを許容できるものとして扱い、残りを拒否するようブラウザに指示する明確な方法です。ただし、オリジンに基づく許可リストでは、XSS 攻撃による最大の脅威であるインライン スクリプト インジェクションは解決されません。攻撃者が、悪意のあるペイロード(<script>sendMyDataToEvilDotCom()</script>)を直接含むスクリプトタグを挿入できる場合、ブラウザには正規のインライン スクリプトタグと区別するメカニズムがありません。CSP は、インライン スクリプトを完全に禁止することでこの問題を解決します。これが確実な唯一の方法です。

この禁止対象には、script タグに直接埋め込まれたスクリプトだけでなく、インライン イベント ハンドラや javascript: URL も含まれます。script タグの内容を外部ファイルに移動し、javascript: URL と <a ... onclick="[JAVASCRIPT]"> を適切な addEventListener() 呼び出しに置き換える必要があります。たとえば、次のコードを書き換えます。

<script>
    function doAmazingThings() {
    alert('YOU AM AMAZING!');
    }
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

次のように記述します。

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert('YOU AM AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('amazing')
    .addEventListener('click', doAmazingThings);
});

書き換えられたコードには、CSP とうまく連携する以上の利点があります。これは、CSP を使用するかどうかにかかわらず、すでにベスト プラクティスです。インライン JavaScript は、不適切な方法で構造と動作を混在させます。 外部リソースを使用すると、ブラウザにとってはキャッシュが容易になり、デベロッパーにとっては理解しやすくなり、コンパイルや圧縮が促進されます。コードを外部リソースに移動する作業を行うと、より優れたコードを書くことができます。

インライン スタイルも同様に扱われます。style 属性と style タグの両方を外部スタイルシートに統合して、驚くほど巧妙なさまざまなデータ引き出し手法に対抗する必要があります。

インライン スクリプトとスタイルが必要な場合は、script-src または style-src ディレクティブで、許可されたソースとして 'unsafe-inline' を追加することで有効にできます。ノンスやハッシュを使用することもできますが(下記参照)、使用すべきではありません。インライン スクリプトの禁止は CSP が提供できる最大のセキュリティであり、インライン スタイルを禁止すると同様にアプリケーションが強化されます。すべてのコードを行から外した後に正しく機能するようにするための事前作業は重要ですが、これはやむを得ず行う価値のあるトレードオフです。

どうしても必要な場合は、

CSP レベル 2 では、暗号ノンス(1 回使用される番号)またはハッシュのいずれかを使用して、特定のインライン スクリプトを許可リストに追加できます。これにより、インライン スクリプトの下位互換性が提供されます。これは面倒な作業ですが、必要なときに便利です。

ノンスを使用するには、スクリプトタグにノンス属性を指定します。この値は、信頼できるソースのリストの値と一致する必要があります。例:

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
    // Some inline code I can't remove yet, but need to asap.
</script>

次に、nonce- キーワードに付加したノンスを script-src ディレクティブに追加します。

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

ノンスはページ リクエストごとに再生成する必要があり、推測できないものにする必要があります。

ハッシュも同じように機能します。スクリプトタグにコードを追加する代わりに、スクリプト自体の SHA ハッシュを作成して script-src ディレクティブに追加します。たとえば、ページに次のコンテンツが含まれているとします。

<script>alert('Hello, world.');</script>

ポリシーには以下の内容を含めます。

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

ここで注意すべき点がいくつかあります。sha*- 接頭辞は、ハッシュを生成するアルゴリズムを指定します。上記の例では、sha256- を使用しています。CSP は sha384-sha512- もサポートしています。ハッシュを生成するときは、<script> タグを含めないでください。また、先頭や末尾の空白文字を含め、大文字や空白文字も重要です。

SHA ハッシュの生成について Google 検索を行うと、さまざまな言語のソリューションが見つかります。Chrome 40 以降では、DevTools を開いてページを再読み込みできます。[コンソール] タブには、インライン スクリプトごとに適切な SHA256 ハッシュを含むエラー メッセージが表示されます。

評価も行う

攻撃者がスクリプトを直接挿入できない場合でも、アプリケーションを騙して本来は不活性なテキストを実行可能な JavaScript に変換し、ユーザーに代わって実行させることができる可能性があります。eval()new Function()setTimeout([string], …)setInterval([string], ...) はすべて、挿入されたテキストによって予期しない悪意のある処理が実行される可能性があるベクトルです。このリスクに対する CSP のデフォルト対応では、これらのベクトルをすべて完全にブロックします。

これは、アプリケーションの構築方法にだけではなく、

  • eval に依存するのではなく、組み込みの JSON.parse を使用して JSON を解析する必要があります。ネイティブ JSON オペレーションは IE8 以降のすべてのブラウザで使用でき、完全に安全です。
  • 文字列ではなく、インライン関数で現在行っている setTimeout または setInterval の呼び出しを書き換えます。例:
setTimeout("document.querySelector('a').style.display = 'none';", 10);

次のように記述する方がよいでしょう。

setTimeout(function () {
    document.querySelector('a').style.display = 'none';
}, 10);
  • 実行時にインライン テンプレートを使用しない: 多くのテンプレート ライブラリでは、実行時のテンプレート生成を高速化するために new Function() を自由に使用します。これは動的プログラミングの優れた応用例ですが、悪意のあるテキストとして評価されるリスクがあります。一部のフレームワークでは CSP がデフォルトでサポートされており、eval がない場合は堅牢なパーサーにフォールバックします。AngularJS の ng-csp ディレクティブが良い例です。

ただし、プリコンパイルが可能なテンプレート言語の方が適しています(たとえば、Handlebars では可能)。テンプレートをプリコンパイルすると、最速のランタイム実装よりもユーザー エクスペリエンスがさらに高速になり、安全性も高まります。eval およびその text-to-JavaScript の兄弟がアプリケーションに不可欠な場合は、'unsafe-eval' を許可されたソースとして script-src ディレクティブに追加することで有効にできますが、おすすめしないことを強くおすすめします。文字列の実行を禁止することで、攻撃者がサイトで不正なコードを実行することははるかに困難になります。

レポート

信頼できないリソースをクライアントサイドでブロックできる CSP の機能は、ユーザーにとって大いに役立ちますが、なんらかの通知をサーバーに送り返すと、そもそも悪意のあるインジェクションを可能とするバグを特定して阻止できるため、非常に便利です。そのためには、report-uri ディレクティブで指定された場所に JSON 形式の違反レポートを POST するようブラウザに指示できます。

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

レポートは次のようになります。

{
    "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
    }
}

ここには、違反が発生したページ(document-uri)、そのページの参照 URL(HTTP ヘッダー フィールドとは異なり、キーにスペルミスはない)、ページのポリシーに違反したリソース(blocked-uri)、違反した特定のディレクティブ(violated-directive)、ページのポリシーの完全なポリシー(original-policy)など、違反の具体的な原因の追跡に役立つ多くの情報が含まれています。

レポート専用

CSP を使い始めたばかりの場合は、厳格なポリシーをユーザーに展開する前に、アプリケーションの現在の状態を評価するのが合理的です。完全なデプロイへの足がかりとして、ブラウザにポリシーのモニタリングを依頼して、違反を報告しますが、制限を適用しないようにすることができます。Content-Security-Policy ヘッダーを送信する代わりに、Content-Security-Policy-Report-Only ヘッダーを送信します。

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

レポート専用モードで指定したポリシーは、制限付きリソースはブロックされませんが、指定したロケーションに違反レポートが送信されます。両方のヘッダーを送信して、1 つのポリシーを適用し、別のポリシーをモニタリングすることもできます。これは、アプリケーションの CSP への変更による影響を評価するのに最適な方法です。新しいポリシーの報告を有効にし、違反レポートをモニタリングし、見つかったバグを修正します。効果に問題がなければ、新しいポリシーの適用を開始します。

実際の使用状況

CSP 1 は Chrome、Safari、Firefox で使用できますが、IE 10 でのサポートは非常に限定されています。詳しくは、caniuse.com をご覧ください。CSP レベル 2 は、Chrome バージョン 40 以降で利用できます。Twitter や Facebook などの大規模なサイトでは、このヘッダーがデプロイされています(Twitter のケーススタディをご覧ください)。この標準は、自社サイトでのデプロイをすぐに開始できます。

アプリケーションのポリシーを作成するための最初のステップは、実際に読み込むリソースを評価することです。アプリでの構成要素を把握したら、その要件に基づいてポリシーを設定します。一般的なユースケースをいくつか紹介し、CSP の保護の範囲内で、どのようにサポートするのが最善かを判断しましょう。

ユースケース #1: ソーシャル メディア ウィジェット

  • Facebook の「いいね!」ボタンにはさまざまな実装オプションがあります。サイトの他の部分から安全にサンドボックス化されている <iframe> バージョンを使用することをおすすめします。適切に機能させるには、child-src https://facebook.com ディレクティブが必要です。デフォルトでは、Facebook が提供する <iframe> コードは相対 URL(//facebook.com)を読み込みます。これを変更して、HTTPS を明示的に指定します(https://facebook.com)。必要でなければ HTTP を使用する理由はありません。

  • Twitter のツイートボタンは、https://platform.twitter.com でホストされているスクリプトとフレームへのアクセス権に依存します。(Twitter も同様に、デフォルトで相対 URL を提供します。ローカルにコピー&ペーストする場合は、コードを編集して HTTPS を指定します)。Twitter から提供された JavaScript スニペットを外部の JavaScript ファイルに移動する限り、script-src https://platform.twitter.com; child-src https://platform.twitter.com で準備完了です。

  • 他のプラットフォームにも同様の要件があり、同様に対処できます。default-src'none' に設定し、ウィジェットを動作させるために有効にする必要があるリソースをコンソールで確認することをおすすめします。

複数のウィジェットを含めるのは簡単です。ポリシー ディレクティブを結合するだけです。必ず、1 つのタイプのリソースをすべて 1 つのディレクティブにマージします。3 つのソーシャル メディア ウィジェットすべてが必要な場合、ポリシーは次のようになります。

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

ユースケース 2: ロックダウン

ここでは、銀行サイトを運営していて、自分で作成したリソースのみを読み込めるようにしたいとします。このシナリオでは、すべてをブロックするデフォルト ポリシー(default-src 'none')から始めて、そこから構築していきます。

銀行が https://cdn.mybank.net の CDN からすべての画像、スタイル、スクリプトを読み込み、XHR 経由で https://api.mybank.com/ に接続して、さまざまなビットのデータを pull するとします。フレームはサイトのローカルページのみに使用されます(サードパーティのオリジンは使用しません)。サイトに Flash もフォントも追加もありません送信できる最も制限の厳しい CSP ヘッダーは次のとおりです。

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

ユースケース 3: SSL のみ

結婚指輪ディスカッション フォーラムの管理者は、すべてのリソースが安全なチャネル経由でのみ読み込まれるようにしたいと考えています。ただし、あまりコードを記述する必要はありません。インラインのスクリプトやスタイルでいっぱいのサードパーティ フォーラム ソフトウェアを書き換えるのは、作業の難しさです。次のポリシーが有効となります。

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

https:default-src で指定されていても、スクリプトとスタイルのディレクティブは自動的にそのソースを継承しません。各ディレクティブは、特定のタイプのリソースのデフォルトを完全に上書きします。

今後について

コンテンツ セキュリティ ポリシー レベル 2 は推奨事項候補です。W3C の Web Application Security Working Group は、この仕様の次回版である Content Security Policy Level 3 の作業をすでに開始しています。

今後追加される機能に興味がある方は、public-webappsec@ メーリング リストのアーカイブをご覧いただくか、ご自身にご参加ください。