コンテンツ セキュリティ ポリシーを使用すると、最新のブラウザにおけるクロスサイト スクリプティング攻撃のリスクと影響を大幅に低減できます。
ウェブのセキュリティ モデルは、同一オリジン ポリシーに基づいています。たとえば、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
からコードを読み込んで実行します。Google はそのコードを信頼していますが、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
です。これで、ブラウザは HTTPS 経由で apis.google.com
から、および現在のページのオリジンから JavaScript をダウンロードして実行できるようになりましたが、他のオリジンからは JavaScript をダウンロードできません。攻撃者がサイトにコードを挿入した場合、ブラウザはエラーをスローし、挿入されたスクリプトは実行しません。
ポリシーは幅広いリソースに適用される
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
- ウェブフォントを配信できるオリジンを指定します。たとえば、
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
- HTTP を HTTPS に変更して URL スキームを書き換えるようユーザー エージェントに指示します。このディレクティブは、書き換えが必要な古い URL が大量にあるウェブサイト向けです。
worker-src
- ワーカー、共有ワーカー、または 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'
を使用すると、eval
のようなテキストから JavaScript へのメカニズムが可能になります。詳細については、eval()
を避けるをご覧ください。
これらのキーワードには一重引用符が必要です。たとえば、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-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
タグと属性を外部のスタイルシートに移動することを強くおすすめします。
インラインのスクリプトとスタイルを一時的に許可する方法
インラインのスクリプトとスタイルを有効にするには、script-src
または style-src
ディレクティブで、許可されたソースとして 'unsafe-inline'
を追加します。CSP レベル 2 では、次のように暗号ノンス(1 回使用される番号)またはハッシュを使用して、特定のインライン スクリプトを許可リストに追加することもできます。
ノンスを使用するには、スクリプトタグにノンス属性を指定します。この値は、信頼できるソースのリストのいずれかの値と一致する必要があります。次に例を示します。
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to as soon as possible.
</script>
script-src
ディレクティブで、nonce-
キーワードの後にノンスを追加します。
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 ハッシュを生成するソリューションは、さまざまな言語で利用できます。Chrome 40 以降の場合は、DevTools を開いてページを再読み込みできます。[コンソール] タブには、各インライン スクリプトの正しい 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()
を頻繁に使用します。これにより、悪意のあるテキストの評価が可能になります。一部のフレームワークでは CSP がデフォルトでサポートされており、eval
がない場合は堅牢なパーサーにフォールバックします。AngularJS の ng-csp ディレクティブが、その良い例です。ただし、代わりに Handlebars などのプリコンパイルが可能なテンプレート言語を使用することをおすすめします。テンプレートをプリコンパイルすると、最速のランタイム実装よりもユーザー エクスペリエンスを高速化でき、サイトのセキュリティも強化できます。
eval()
などのテキストから JavaScript への関数がアプリケーションに不可欠な場合は、許可されたソースとして 'unsafe-eval'
を script-src
ディレクティブに追加することで、これらの機能を有効にできます。コード インジェクションのリスクがあるため、この方法はおすすめしません。
ポリシー違反を報告する
悪意のあるインジェクションの可能性があるバグをサーバーに通知するには、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;
レポート専用モードで指定されたポリシーは、制限付きリソースはブロックされませんが、指定したロケーションに違反レポートを送信します。両方のヘッダーを送信して、あるポリシーをモニタリングしながら別のポリシーを適用することもできます。これは、現在のポリシーを適用しながら 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'
https:
は default-src
で指定されますが、スクリプトとスタイル ディレクティブは、そのソースを自動的に継承しません。各ディレクティブは、その特定の種類のリソースのデフォルトを上書きします。
CSP 標準の開発
コンテンツ セキュリティ ポリシー レベル 2 は、W3C の推奨標準です。W3C のウェブ アプリケーション セキュリティ ワーキング グループは、この仕様の次期版であるコンテンツ セキュリティ ポリシー レベル 3 を開発しています。
今後リリースされる機能については、public-webappsec@ メーリング リスト アーカイブをご覧ください。