コンテンツ セキュリティ ポリシーを使用すると、最新のブラウザでクロスサイト スクリプティング攻撃のリスクと影響を大幅に軽減できます。
ウェブのセキュリティ モデルは、同一オリジン ポリシーに基づいています。たとえば、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-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
基本的な 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'
では、テキストから JavaScript のメカニズム(eval
など)を使用できます。詳細については、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 は、コードがわかりづらくなるように構造と動作を組み合わせます。また、キャッシュとコンパイルもより複雑です。コードを外部リソースに移動すると、ページのパフォーマンスが向上します。
CSS ベースのデータの引き出し攻撃からサイトを保護するために、インラインの style
タグと属性を外部スタイルシートに移動することを強くおすすめします。
インライン スクリプトとスタイルを一時的に許可する方法
インライン スクリプトとスタイルを有効にするには、'unsafe-inline'
を許可されたソースとして script-src
または style-src
ディレクティブに追加します。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 形式の違反レポートを送信するようにブラウザに指示します。
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 の [Like] ボタンには、いくつかの実装オプションがあります。
<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@ メーリング リストのアーカイブをご覧ください。