コンテンツ セキュリティ ポリシーにより、最新のブラウザでのクロスサイト スクリプティング攻撃のリスクと影響を大幅に軽減できます。
ウェブのセキュリティ モデルは、同一オリジン ポリシーに基づいています。たとえば、https://mybank.com のコードは https://mybank.com のデータにのみアクセスでき、https://evil.example.com へのアクセスは許可されません。理論上、各オリジンはウェブの残りの部分から隔離されるため、デベロッパーは安全なサンドボックスでビルドできます。しかし実際には、攻撃者はシステムを妨害する方法を見つけています。
たとえば、クロスサイト スクリプティング(XSS)攻撃は、意図するコンテンツとともに悪質なコードを提供するようにサイトを仕向けることによって、同一生成元ポリシーを回避します。これは大きな問題です。ブラウザは、ページに表示されるすべてのコードを、そのページのセキュリティ オリジンに正当に含まれているものとして信頼するからです。XSS チートシートは、攻撃者が悪質なコードを注入することでこの信頼を侵害するために使用する手法の古い代表的な例です。攻撃者がなんらかのコードの注入に成功すると、ユーザー セッションが侵害され、機密情報へのアクセス権が取得されます。
このページでは、最新のブラウザで XSS 攻撃のリスクと影響を軽減するための戦略として、コンテンツ セキュリティ ポリシー(CSP)について概説します。
CSP のコンポーネント
効果的な CSP を実装するには、次の手順を実施します。
- 許可リストを使用して、許可または不許可の対象をクライアントに指示します。
- 使用できるディレクティブについて説明します。
- ディレクティブに指定できるキーワードについて説明します。
- インライン コードと
eval()の使用を制限します。 - ポリシーを適用する前に、ポリシー違反をサーバーに報告します。
ソースの許可リスト
XSS 攻撃は、ブラウザが、アプリケーションに含まれるスクリプトと、悪意のある第三者が注入したスクリプトを区別できなくなることを悪用します。たとえば、このページの下部にある Google +1 ボタンは、オリジンがこのページのオリジンという条件で、https://apis.google.com/js/plusone.js から取得したコードを読み込み、実行します。私たちはこのコードを信頼していますが、ブラウザに対して、apis.google.com から取得したコードは安全に実行できるが apis.evil.example.com から取得したコードは問題がある、と見分けることは期待できません。このブラウザは参照元にかかわらず、ただ素直にページが要求するコードをダウンロードして実行します。
CSP の Content-Security-Policy HTTP ヘッダーを使用すると、信頼できるコンテンツのソースの許可リストを作成し、ブラウザにこれらのソースからのリソースのみを実行またはレンダリングするように指示できます。攻撃者がスクリプトを注入するセキュリティ ホールを見つけたとしても、許可リストに一致しなければそのスクリプトは実行されません。
apis.google.com は有効なコードを配信すると信頼しています。また、自身のドメインも同様に信頼できます。次のポリシーの例では、スクリプトがこの 2 つのリソースのいずれかから取得された場合にのみ実行を許可します。
Content-Security-Policy: script-src 'self' https://apis.google.com
script-src は、ページのスクリプト関連の権限を制御するディレクティブです。このヘッダー 'self' はスクリプトの有効な参照元として、https://apis.google.com は別の参照元として指定します。ブラウザは、apis.google.com から HTTPS を介して JavaScript をダウンロードして実行できるようになりました。また、現在のページのオリジンからも JavaScript をダウンロードして実行できるようになりましたが、他のオリジンからは実行できません。攻撃者がサイトにコードを挿入すると、ブラウザはエラーをスローし、挿入されたスクリプトを実行しません。
幅広いリソースに適用するポリシー
CSP には、ページで読み込みが許可されるリソースをきめ細かく制御できるポリシー ディレクティブのセット(前の例の script-src など)が用意されています。
次のリストは、レベル 2 の残りのリソース ディレクティブの概要を示しています。レベル 3 仕様のドラフトは作成されていますが、主要なブラウザではほとんど実装されていません。
base-uri- ページの
<base>要素に表示できる URL を制限します。 child-src- ワーカーと埋め込まれたフレーム コンテンツの URL を一覧表示します。たとえば、
child-src https://youtube.comは YouTube の動画を埋め込むことができますが、他のオリジンの動画は埋め込むことができません。 connect-src- XHR、WebSockets、EventSource を使用して接続できるオリジンを制限します。
font-src- ウェブフォントを配信できるオリジンを指定します。たとえば、
font-src https://themes.googleusercontent.comを使用して Google のウェブフォントを許可できます。 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- ページがスタイルシートを使用できるオリジンを制限します。
upgrade-insecure-requests- ユーザー エージェントに指示して URL スキーマを書き直し、HTTP を HTTPS に変更します。このディレクティブは、書き直しが必要な古い URL が多数存在するウェブサイトに使用します。
worker-src- Worker、Shared Worker、Service Worker として読み込まれる URL を制限する CSP レベル 3 ディレクティブ。2017 年 7 月現在、このディレクティブの実装は制限されています。
デフォルトでは、特定のディレクティブでポリシーを設定しない限り、ブラウザは、制限なしで任意のオリジンから関連リソースを読み込みます。デフォルトをオーバーライドするには、default-src ディレクティブを指定します。このディレクティブは、-src で終わる未指定のディレクティブのデフォルトを定義します。たとえば、default-src を https://example.com に設定し、font-src ディレクティブを指定しない場合、https://example.com からのみフォントを読み込むことができます。
次のディレクティブは、フォールバックとして default-src を使用しません。設定しない場合はすべて許可したのと同じです。
base-uriform-actionframe-ancestorsplugin-typesreport-urisandbox
基本的な CSP 構文
CSP ディレクティブを使用するには、HTTP ヘッダーにディレクティブをコロン区切りで記述します。次のように、特定のタイプの必須リソースをすべて 1 つのディレクティブに記述してください。
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'
実装の詳細
最新のブラウザは、接頭辞のない Content-Security-Policy ヘッダーをサポートしています。これは推奨されるヘッダーです。オンライン チュートリアルで説明されている X-WebKit-CSP ヘッダーと X-Content-Security-Policy ヘッダーは非推奨になりました。
CSP はページごとに定義されます。保護する必要があるレスポンスには毎回、この HTTP ヘッダーを付けて送信する必要があります。これにより、特定のニーズに基づいて特定のページのポリシーを微調整できます。たとえば、サイトに +1 ボタンが存在するページもあれば、ないページもあります。その場合、必要なときにだけボタンのコードの読み込みを許可できます。
各ディレクティブの参照元リストは柔軟に指定できます。スキーム(data:、https:)で参照元を指定したり、選択的にホスト名のみを指定したり(example.com: スキームやポートを問わず、このホストのオリジンすべてに一致)、完全修飾 URI(https://example.com:443: HTTPS のみ、example.com のみ、ポート 443 のみと一致)など、幅広く指定できます。ワイルドカードも使用できますが、スキーム、ポート、またはホスト名の一番左端のみに限定されます。*://*.example.com:* は、example.com のすべてのサブドメインに一致し、スキームとポートはどれでも使用できます(ただし、example.com 自体は一致しません)。
参照元リストには、次の 4 つのキーワードも使用できます。
'none'は何も一致しません。'self': 現在のオリジンと一致します。ただしサブドメインは除外されます。'unsafe-inline': インライン JavaScript と CSS を許可します。詳細については、インライン コードを避けるをご覧ください。'unsafe-eval'は、evalなどの text-to-JavaScript の仕組みを許可します。詳細については、eval()を回避するをご覧ください。
これらのキーワードには一重引用符が必要です。たとえば、script-src 'self'(引用符付き)は、現在のホストからの JavaScript の実行を許可します。script-src self(引用符なし)は、(現在のホストからではなく)「self」という名前のサーバーからの JavaScript を許可します。これはおそらく意図と異なるでしょう。
ページをサンドボックス化する
もう 1 つ説明すべきディレクティブがあります。sandbox です。これまで説明してきたディレクティブとは若干異なり、ページが読み込めるリソースにではなく、ページで実行できるアクションに制限を加えます。sandbox ディレクティブが存在する場合、ページは sandbox 属性を指定した <iframe> 内に読み込まれるように処理されます。このディレクティブは、ページを一意のオリジンに限定したり、フォームの送信を禁止したり、ページに幅広い効果をもたらします。このページの範囲からややはずれますが、有効なサンドボックス属性の完全な詳細については、HTML5 仕様の「Sandboxing」セクションをご覧ください。
メタタグ
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-ancestors、report-uri、sandbox には使用できません。
インライン コードの使用を避ける
CSP ディレクティブで使用されるオリジンベースの許可リストは強力ですが、XSS 攻撃によってもたらされる最大の脅威であるインライン スクリプト挿入を解決することはできません。攻撃者が、悪意のあるペイロード(<script>sendMyDataToEvilDotCom()</script> など)を直接含むスクリプトタグを挿入できる場合、ブラウザはそれを正当なインライン スクリプトタグと区別できません。CSP は、インライン スクリプトを完全に禁止することでこの問題を解決します。
この禁止には、script タグに直接組み込まれたスクリプトに限らず、インライン イベント ハンドラと javascript: URL も含まれます。script タグのコンテンツを外部ファイルに移動し、javascript: URL と <a ...
onclick="[JAVASCRIPT]"> を適切な addEventListener() 呼び出しに置き換える必要があります。たとえば、次のコードを書き換えるとします。
<script>
function doAmazingThings() {
alert('YOU ARE 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 ARE AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('amazing')
.addEventListener('click', doAmazingThings);
});
書き換えられたコードは CSP と互換性があるだけでなく、ウェブ デザインのベスト プラクティスにも準拠しています。インライン JavaScript は、コードがわかりづらくなるように構造と動作を組み合わせます。また、キャッシュとコンパイルが複雑になります。コードを外部リソースに移動すると、ページのパフォーマンスが向上します。
インラインの style タグと属性を外部スタイルシートに移動することも、CSS ベースのデータ抽出攻撃からサイトを保護するために強く推奨されます。
インライン スクリプトとスタイルを一時的に許可する方法
インライン スクリプトとスタイルを有効にするには、script-src または style-src ディレクティブで許可される参照元として 'unsafe-inline' を追加します。CSP Level 2 では、暗号化されたナンス(1 回だけ使用する数値)またはハッシュを使用して、特定のインライン スクリプトを許可リストに追加することもできます。
ナンスを使用するには、script タグに nonce 属性を指定します。この値は、信頼できる参照元リストに含まれる値と一致する必要があります。次に例を示します。
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to as soon as possible.
</script>
nonce- キーワードの後に、script-src ディレクティブにナンスを追加します。
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
ナンスはページ リクエストのたびに再度生成する必要があり、推測できない値にする必要がある点に注意してください。
ハッシュも同じように機能します。script タグにコードを追加する代わりに、スクリプト自体の SHA ハッシュを作成して script-src ディレクティブに追加します。たとえば、ページに次のような内容が含まれているとします。
<script>alert('Hello, world.');</script>
ポリシーには次のものを含める必要があります。
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
sha*- 接頭辞は、ハッシュを生成するアルゴリズムを示します。上記の例では sha256- を使用していますが、CSP では sha384- と sha512- もサポートされています。ハッシュを生成する際は、<script> タグを省略します。先頭や末尾のスペースなど、大文字とスペースも影響します。
SHA ハッシュを生成するソリューションは、任意の言語で利用できます。Chrome 40 以降を使用して、DevTools を開き、ページを再読み込みします。[Console] タブには、インライン スクリプトごとに、エラー メッセージと正しい SHA-256 ハッシュが表示されます。
eval() を避ける
攻撃者がスクリプトを直接挿入できない場合でも、攻撃者はアプリケーションを騙して入力テキストを実行可能な 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()を頻繁に使用します。これにより、悪意のあるテキストを評価できます。一部のフレームワークでは、evalが存在しない場合は堅牢なパーサーにフォールバックし、CSP をサポートしています。AngularJS の ng-csp ディレクティブがその好例です。代わりに、事前コンパイルを提供するテンプレート言語(Handlebars など)を使用することをおすすめします。テンプレートをプリコンパイルすると、最速のランタイム実装よりもユーザー エクスペリエンスがさらに高速化され、サイトのセキュリティも強化されます。
eval() などの text-to-JavaScript 関数がアプリケーションに欠かせない場合は、script-src ディレクティブで許可される参照元として 'unsafe-eval' を追加することで有効にできます。コード挿入のリスクがあるため、これを行わないことを強くおすすめします。
ポリシー違反を報告する
悪意のある挿入を許す可能性のあるバグをサーバーに通知するには、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)、そのページの referrer、ページのポリシーに違反したリソース(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;
report-only モードで指定されたポリシーは、制限対象リソースをブロックせず、違反レポートを指定した場所に送信します。両方のヘッダーを送信して、1 つのポリシーを適用し、もう 1 つのポリシーを監視することもできます。これは、現在のポリシーを適用しながら CSP の変更をテストする方法として非常におすすめです。新しいポリシーのレポートを有効にして、違反レポートを監視し、見つかったバグを修正します。新しいポリシーに満足できたら、その適用を開始します。
実際の使い方
アプリのポリシーを作成するための最初のステップは、アプリが読み込むリソースを評価することです。アプリの構造を理解したら、その要件に基づいてポリシーを作成します。以降のセクションでは、一般的なユースケースと、CSP ガイドラインに沿ってそれらをサポートするための意思決定プロセスについて説明します。
ソーシャル メディア ウィジェット
- Facebook の高評価ボタンには、実装方法がいくつかあります。
<iframe>バージョンを使用して、サイトの他の部分から切り離すことをおすすめします。正しく機能させるには、child-src https://facebook.comディレクティブが必要です。 - X のツイート ボタンは、スクリプトへのアクセスに依存しています。提供されたスクリプトを外部 JavaScript ファイルに移動し、ディレクティブ
script-src https://platform.twitter.com; child-src https://platform.twitter.comを使用します。 - その他のプラットフォームにも類似の要件があり、同じように対処できます。これらのリソースをテストするには、
default-srcを'none'に設定し、コンソールを監視して有効にする必要があるリソースを特定することをおすすめします。
複数のウィジェットを使用するには、ディレクティブを次のように組み合わせます。
script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com
ロックダウン
一部のウェブサイトでは、ローカル リソースのみを読み込めるようにする必要があります。次の例では、すべてをブロックするデフォルト ポリシー(default-src 'none')から開始して、銀行サイトの CSP を開発します。
このサイトでは、すべての画像、スタイル、スクリプトを https://cdn.mybank.net にある CDN から読み込み、XHR を使用して https://api.mybank.com/ に接続してデータを取得します。フレームが使用されていますが、サイトのローカル ページのみです(サードパーティのオリジンはありません)。サイトに 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'
SSL のみ
次の CSP は、フォーラムのすべてのリソースを安全なチャンネルからのみ読み込めるようにしたいと考えているフォーラム管理者向けのものです。この管理者はコーディングの経験がなく、インライン スクリプトとスタイルが多数使われているサードパーティ フォーラム ソフトウェアを書き直すリソースがありません。
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
default-src で https: が指定されていますが、スクリプトとスタイルのディレクティブは、その参照元を自動的に継承することはありません。各ディレクティブは、特定のタイプのリソースのデフォルト値を上書きします。
CSP 標準の開発
Content Security Policy Level 2 は W3C の推奨標準です。W3C の Web Application Security Working Group は、仕様の次のイテレーションである Content Security Policy Level 3 を開発しています。
今後の機能に関する議論については、public-webappsec@ メーリング リストのアーカイブをご覧ください。