コンテンツセキュリティポリシー (CSP) の実装
HTTP の Content-Security-Policy ヘッダーは、サイト上で読み込まれたコードや、そのコードが実行できることについて、きめ細かな制御を提供します。
問題
この記事で主に取り上げる問題は、クロスサイトスクリプティング (XSS) 攻撃です。これらは一般的に、サイトのリソースが読み込まれるソースに対する制御と認識が不足していることが原因です。サイトが大きくなり複雑化するにつれ、またJavaScript ライブラリーなどのサードパーティー製リソースへの依存度が高まるにつれ、この問題への対処はますます困難になっています。
CSP は、他の記事で取り上げられている他の問題の解決にも役立ちます。
- サイトが
<iframe>要素に埋め込まれるのを防ぐことで、クリックジャッキングを防止します。これは、CSP のframe-ancestorsディレクティブを使用して行います。 - すべての HTTP 接続を HTTPS にアップグレードすることで、中間者攻撃 (MiTM) を防ぎます。これには、CSP の
upgrade-insecure-requestsディレクティブが役立ちます。保護されていないリクエストのアップグレードをご覧ください。
解決策
厳格な CSP を実装することが、CSP を用いて XSS の脆弱性を軽減する最善の方法です。このディレクティブでは、nonce- または hash- ベースのフェッチディレクティブを使用し、正しいノンスまたはハッシュを含むスクリプトやスタイルのみが実行されるように実現します。ハッカーによって挿入された JavaScript は、単純に実行されません。
厳格な CSP は次のことも実現します。
- 安全でないインライン JavaScript、つまり
onclickなどの イベントハンドラー属性の使用を無効にします。これにより、適切にエスケープされていないユーザー入力が、ブラウザーによって JavaScript として解釈されるのを防ぐことができます。 script-srcディレクティブのもう一つの効果として、eval()などの危険な API 呼び出しの使用を無効にします。object-src 'none'を使用して、すべてのオブジェクトの埋め込みを無効にします。base-uri 'none';を使用して、<base>要素によるベース URI の設定を無効にします。
厳格な CSP は、スクリプトを実行するドメインを指定する場所ベースのポリシー(別名:許可リストポリシー)よりも推奨されます。これは、許可リスト方式では安全でないドメインが許可されてしまうことが多く、CSP を導入する本来の目的が損なわれてしまうためです。また、特に多くのサードパーティー製スクリプトを必要とするサービスの動作を許可しようとする場合、許可リストが非常に大規模で扱いにくくなる恐れがあります。
CSP を実装する手順
厳格な CSP を実装した後、そのポリシーの影響で読み込みに失敗しているリソースを特定し、これらの課題を回避するための対策を講じてください。
メモ:
Content-Security-Policy ヘッダーを使用して実際の CSP を実装する前に、まず HTTP の Content-Security-Policy-Report-Only ヘッダーを使用してテストすることをお勧めします。詳細は、下記レポート専用 CSP をご覧ください。
- ノンスとハッシュのどちらを使用するか選択してください。コンテンツを動的に生成できる場合はノンスを使用しましょう。静的コンテンツを提供する必要がある場合はハッシュを使用しましょう。
- 解決策の節で概要を説明しているように、厳格な CSP を実装してください。実行したい外部および内部のスクリプト(
<script>要素を介して記載されるもの)には、サーバーによってnonce属性に正しいノンスが挿入されていることを確認してください。代わりにハッシュを使用する場合は、外部スクリプトのintegrity属性に正しいハッシュが挿入されている必要があります。 - 許可されたスクリプトがサードパーティースクリプトを読み込もうとした場合、それらのスクリプトには必要なノンスやハッシュを保有していないため、読み込みに失敗します。この問題を緩和するには、
strict-dynamicディレクティブを追加してください。これにより、まず最初のスクリプトによって読み込まれたスクリプトは、明示的にノンスやハッシュが指定されなくても、同じレベルの信頼を得ることができます。 - インラインのイベントハンドラーや
eval()など、厳格な CSP で許可されていないパターンを書き換えてください。例えば、インラインのイベントハンドラーを、スクリプト内でのaddEventListener()の呼び出しに置き換えてください。 - サイトが埋め込み機能を記載する必要がない限り、
object-src 'none'を使用してその実行を無効にするべきです。 eval()の使用を除去できない場合は、厳格な CSP にunsafe-evalキーワードを追加してすることができるが、これにより CSP のセキュリティは大幅に低下します。- イベントハンドラーの属性を除去できない場合は、厳格な CSP に
unsafe-hashesキーワードを追加することで、それらを許可することができます。これは多少のリスクはありますが、すべてのインライン JavaScript を許可するよりははるかに安全です。
厳格な CSP を導入できない場合でも、許可リストベースの CSP を採用する方が、何も設定しないよりははるかにましです。また、default-src https: のような CSP でも、安全でないインライン実行や eval() を無効にし、HTTPS 経由でのリソース(画像、フォント、スクリプトなど)の読み込みのみを許可することで、ある程度の保護効果は得られます。
警告: 可能な限り、CSP 内に安全でないソースを記載しないようにしてください。例えば次のようなものです。
unsafe-inlinedata:URI をscript-src,object-src,default-srcの中で使用する- ソースやフォーム送信先が不必要に広範囲にわたっている。
Content-Security-Policy ヘッダーを使用できない場合、代わりにページに <meta http-equiv="Content-Security-Policy" content="…"> 要素を記載することができます。これは、文書の <head> 内に最初に現れる <meta> 要素である必要があります。
レポート専用 CSP
Content-Security-Policy ヘッダーを使用して実際の CSP を実装する前に、まず HTTP の Content-Security-Policy-Report-Only ヘッダーを使用してテストすることをお勧めします。これにより、そのポリシーで違反が発生していたかどうかを確認することができます。
ウェブサイトでは、report-to および report-uri という報告ディレクティブを使用しましょう。これらにより、ブラウザーはエンドポイント(report-to の場合は Reporting-Endpoints ヘッダーで指定されたもの)に対して、 CSP 違反に関する JSON 形式のレポートを POST で送信します。これにより、CSP 違反をすばやく検知し、修正することができます。
メモ:
report-to ディレクティブは、非推奨となった report-uri ディレクティブよりも推奨されます。ただし、report-to はまだすべてのブラウザーで完全に対応していないため、両方がまだ必要です。