多くの開発者は、アプリにコンテンツセキュリティポリシー(CSP)ヘッダーを追加すべきだと知っています。しかし、実際に役に立つものを持っているのは少数です。そのポリシーは、あまりに緩和されていて目的を達成できず、あるいはサイトの半分を壊して不満を抱かせ、結果として削除されてしまいます。
このガイドは、CSPが何を実行するか、どのディレクティブが重要か、そしてアプリを壊さずに実際に攻撃をブロックするヘッダーを書く方法を説明します。
CSPが実際に何をやっているか
コンテンツセキュリティポリシーは、ブラウザがリソースをどこから読み込むことができるかをブラウザに伝えるものです。スクリプトはこのドメインのみから読み込み、スタイルはそのCDNから、画像はどこからでも読み込み、インラインスクリプトは使用しない。
ブラウザがCSPヘッダーを受け取ると、それらのルールを実行前に強制的に適用します。もしクロスサイトスクリプティング(XSS)攻撃がHTMLに悪意のあるスクリプトを注入した場合、CSPはブラウザレベルでそれをブロックします——あなたの入力クリーン化がスクリプトを通過させた場合でも同様です。
これが核心的な価値です:CSPは入力検証が失敗した場合の、2番目の防御ラインになります。
重要なディレクティブ
CSPには数十のディレクティブがありますが、ほとんどの生産環境のヘッダーは6つだけが必要です:
| ディレクティブ | どのようなものを制限するか | 一般的な誤り |
|---|---|---|
default-src |
明示的にリストされていないすべてのリソースタイプのためのフォールバック | 設定 'self'、そしてフォントやフレームがカバーされていないこと |
script-src |
JavaScriptのソースURLおよびインライン実行 | を追加する 'unsafe-inline' コンソールエラーを無視するため |
style-src |
CSSのソースURLおよびインラインスタイルブロック | CDNやJSライブラリによって注入されたインラインスタイルを忘れる |
img-src |
画像ソース、データURIを含む | を欠いている data: ベース64画像に対して、 blob: キャンバスエクスポートに対して、 |
connect-src |
XHR、fetch、WebSocket、およびEventSourceの目的地 | 分析エンドポイントやAPIサブドメインを忘れる |
frame-ancestors |
サイトを埋め込むことができるオリジン <iframe> |
完全にスキップしている——クリックジャッキングが開かれている |
frame-ancestors 古い X-Frame-Options ヘッダーを置き換えており、他のCSPカバレッジが完全でなくても追加すべきです。
なぜ unsafe-inline ポイントを破壊する
を追加すると、ブラウザに「どこからでも見つかったスクリプトタグを実行する」という指示を与えます。これはXSS攻撃によって注入されたスクリプトを含む場合も含みます。 'unsafe-inline' に script-src、あなたはブラウザにこう伝えています:どこから来ても見つけたスクリプトタグを実行してください。これはXSS攻撃によって注入されたスクリプトを含みます。
'unsafe-eval' はさらに悪く、 eval(), Function()と、 setTimeout("string")を許容しており、それらは他の清潔なコードベースでも一般的な攻撃ベクトルです。
これらのドキュメントは、正当なスクリプトのソースを記述するだけで、実際の注入保護を提供しません。攻撃者は、正当なスクリプトがどこから読み込まれているかに関わらず、インラインで自分のスクリプトを注入できるのです。
正しい方法:ノンスとハッシュ
正当なインラインスクリプトがある場合、ポリシーを放棄せずに2つの選択肢があります:
ノンス ページ読み込みごとに一意のトークンを生成し、サーバーがCSPヘッダーおよびインラインスクリプトの nonce 属性にそれを追加します。ノンスが一致するスクリプトのみが実行されます。
<!-- CSP header -->
Content-Security-Policy: script-src 'nonce-abc123xyz'
<!-- Inline script with matching nonce -->
<script nonce="abc123xyz">
window.analyticsId = '...';
</script>
ハッシュ 正確なスクリプトコンテンツのSHA-256ハッシュを計算し、それを script-src に追加します。ブラウザはその特定のスクリプトを実行しますが、それ以外は実行しません。
Content-Security-Policy: script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='
ノンスはコンテンツがリクエストごとに変化する動的ページに適しており、ハッシュはデプロイ間で変化しない静的スクリプトに適しています。
デプロイ前に報告のみモードで実行する
既存のサイトにCSPを追加する前にテストを行わないことは、生産環境でサイトを破壊する原因になります。まず、報告のみのヘッダーを使用します:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{random}'; report-uri /csp-violations
ブラウザはルールを適用し、違反を報告しますが、まだ何もブロッキングしません。この違反ログは、ポリシーを実行モードに切り替える前に追加すべきものすべてを正確に教えてくれます。
ほとんどのCSPデプロイがサイトを壊すためにこのステップをスキップしています。
典型的なSaaSアプリ向けの実用的なCSP
ここに、CDN、Google Analytics、Stripeを使用するアプリ向けの生産用ヘッダーを示します。各ディレクティブは注釈されています:
Content-Security-Policy:
# Tight default: only load from your own origin
default-src 'self';
# Scripts: your origin, GA tag manager via nonce, Stripe.js
script-src 'self' https://www.googletagmanager.com https://js.stripe.com 'nonce-{SERVER_NONCE}';
# Styles: your origin and Google Fonts CSS
style-src 'self' https://fonts.googleapis.com;
# Fonts: your origin and Google Fonts CDN
font-src 'self' https://fonts.gstatic.com;
# Images: your origin, CDN subdomain, and base64 data URIs
img-src 'self' https://cdn.yourdomain.com data:;
# Fetch/XHR: your API, GA collection endpoint, Stripe API
connect-src 'self' https://www.google-analytics.com https://api.stripe.com;
# Stripe renders its fields in an iframe
frame-src https://js.stripe.com;
# Nobody should be framing your app
frame-ancestors 'none';
# Block <object> and <embed> entirely
object-src 'none';
# Force HTTP requests to upgrade to HTTPS
upgrade-insecure-requests;
Express(Node.js) {SERVER_NONCE} 暗号的にランダムな値を生成されたもので、たとえばPHPの base64_encode(random_bytes(16)) またはNode.jsの crypto.randomBytes(16).toString('base64') です。
一般的なCSPの誤り
広範なワイルドカード。 script-src * すべてのオリジンからのスクリプトを許容します。あなたは実質的にスクリプトポリシーをまったく持っていないことになります。
サブドメインを忘れる。 'self' 正確なオリジンのみをカバーしています。 https://api.yourdomain.com は別のオリジンであり、 connect-src.
の下に明示的な項目が必要です。 frame-ancestors. クリックジャッキング保護はXSS保護とは独立しています。それらのディレクティブセットから別に追加してください。
CSPを複数の場所で設定する。 CDNとアプリがそれぞれCSPヘッダーを設定している場合、動作が予測できません。1つのレイヤーで設定するべきです——通常はオリジンサーバーまたはリバースプロキシです。
ワイルドカードを使用することはAPIを開放する最も簡単な方法ですが、認証情報が必要な場合にその効果が失われます。認証情報が要求される場合、ワイルドカードはブラウザによって拒否されます。正確なオリジンを指定する必要があります: report-uri ハンドラーなし。 違反は、影響を受けるページ読み込みごとにPOSTリクエストをあなたのエンドポイントに生成します。それらを処理するか、Report URIのようなサービスに指す必要があります。
作成に手間をかけないCSPヘッダー
スクリプト、スタイル、フォント、画像、API呼び出しからページがリソースを読み込むすべてのドメインを追跡するのは、すぐに面倒になります。オンラインの CSPヘッダー生成器 を設定し、各ディレクティブを視覚的に構成して、生産用のヘッダーを直接サーバー設定に貼り付けることができます。
それを出発点として、開発ツールで実際のネットワークリクエストを確認し、生成器が漏らしたものを発見してください。
CSPが緩すぎる場合、それは装飾にすぎません。厳しすぎる場合、ユーザーの使用を妨げます。報告のみモードから始め、そこから緩和し、インラインスクリプトが避けられない場合はノンスを使用してください。あなたのポリシーは攻撃者の仕事の難しさを高めるべきであり、あなたの仕事の難しさを高めるべきではありません。
