Webサイトのコンテンツセキュリティポリシー:CSPの設定・バイパスリスク・導入
1. CSPが実際に防ぐもの
CSPはHTTPレスポンスヘッダーでルールを配信し、ブラウザはそれに従ってどのリソースを読み込めるか判断します。主に2つの問題を防ぎます。XSSで注入された悪意あるスクリプトと、許可されていない第三者ドメインがページに埋め込まれることです。
これはファイアウォールではありません。CSPはSQLインジェクションもサーバーへの侵入も防げません。管理するのはブラウザ側のリソース読み込みです。HTTPSやHSTSとは連携する関係で、HTTPSの設定方法は独立サイトのHTTPS設定。
独立サイトでよくある悩みは第三者スクリプトの多さです。GA4、GTM、Hotjar、チャットサポート、広告ピクセルなど。スクリプトを追加するたびにCSPのホワイトリストにドメインを1つ足さないと、ブラウザにブロックされます。第三者スクリプトの管理は独立サイトの第三者スクリプト管理。
見落とされがちな事実として、CSPはSEOにほとんど悪影響を与えません。Googlebot自体はJS以外のリソース制限を実行しないため、CSPを正しく設定してもインデックスに影響しません。CSPがクロールを妨げるという心配は不要です。
| ディレクティブ | 管理対象 | 推奨値 |
|---|---|---|
| default-src | 全リソースの受け皿 | ‘self’ |
| script-src | JSスクリプトの取得元 | ‘self’ + 解析ドメイン |
| style-src | CSSの取得元 | ‘self’ ‘unsafe-inline’ |
| img-src | 画像の取得元 | ‘self’ data: + CDN |
| object-src | プラグイン | ‘none’(必ず無効化) |
| frame-ancestors | 本站を埋め込めるサイト | ‘none’ |
2. 3段階の導入:レポート、整理、強制
最もよくある間違いは、いきなりContent-Security-Policyヘッダーを付けることです。これにより、ホワイトリストにないサイト内の全スクリプトが即座にブロックされ、GA4、チャットサポート、ピクセルがすべて停止し、担当部署からすぐ苦情が来ます。
正しい方法は、まずContent-Security-Policy-Report-Onlyヘッダーを使うことです。ブラウザは違反を報告するだけでブロックせず、2週間ログを収集して、実際に使われている全ドメインをホワイトリストに追加します。
シナリオ例1:WordPressサイトにCSPを追加し、まずReport-Onlyを配信します。
2日後のレポートにgoogletagmanager.com、hotjar.com、youtube.comの3つのドメインが現れます。
1つずつscript-srcとframe-srcに追加します。2週間後、レポートが1日1桁台になれば強制モードに切り替えます。
| フェーズ | レスポンスヘッダー | 行為 | 期間 |
|---|---|---|---|
| ステップ1 | Content-Security-Policy-Report-Only | レポートのみ、ブロックなし | 14日間 |
| ステップ2 | 同様に、段階的にホワイトリストを追加 | 違反は日ごとに減少 | 一桁台になるまで |
| ステップ3 | Content-Security-Policy | 実際にブロック | 長期 |
| 全期間 | report-toまたはreport-uriを追加 | 違反レポートを収集 | 常時有効 |
インラインスクリプトが最大の落とし穴です。WordPressのテーマや多くのプラグインはページ内に直接<script>を書き込むため、CSPはデフォルトでインラインをブロックします。対処法は2つ。1つはインラインスクリプトを外部jsファイルに切り出す方法、もう1つはsha256ハッシュで該当部分だけを許可する方法です。安易にunsafe-inlineを有効にするのは避けてください。
style-srcはscript-srcより緩めで構いません。Gutenbergエディタや多くのテーマはインラインstyleを出力するため、style-srcに一時的にunsafe-inlineを残しても問題は小さいです。
ただしscript-srcでunsafe-inlineを有効にするのは絶対に避けてください。ここがCSPによるXSS対策の核心です。2つのディレクティブは緩さの基準を分けて考える必要があります。
三、回避リスク:CSPを形骸化させない
CSPは設定を誤ると意味がありません。以下の3つの書き方でこの壁は無効化されます。
1つ目はunsafe-inlineの有効化です。あらゆるインラインスクリプトの実行を許すため、XSSで注入された悪意あるスクリプトも動き、CSPの核心が破られます。2つ目はワイルドカード*の指定で、これは配信元を制限しないのと同じです。3つ目はobject-srcの漏れで、古いブラウザではFlashやプラグイン経由で回避されます。
シナリオ例2:手軽さを優先してscript-srcに’unsafe-inline’を書いてしまった。
その2か月後、サイトにインラインのフィッシングスクリプトを注入され、CSPはまったく防げませんでした。
正しい方法はnonceの使用です。リクエストごとに使い捨ての乱数を生成し、その値を持つインラインスクリプトだけを許可します。
| 誤った書き方 | リスク | 代替案 |
|---|---|---|
| script-src * | 任意のサードパーティスクリプトが読み込める | ホワイトリストのドメインを個別に列挙 |
| script-src ‘unsafe-inline’ | XSSで注入されたスクリプトがそのまま実行される | 外部ファイルに切り出すかnonce/ハッシュを使う |
| object-srcを書かない | 古いプラグインが悪用されうる | object-src ‘none’ |
| frame-ancestorsを設定しない | ページが悪意あるiframeに埋め込まれる | frame-ancestors ‘none’ |
CSPとコンプライアンスは別物です。GDPRのCookie同意はまた別の仕組みで、詳しくは独立サイトのGDPR対応をご覧ください。CSPが防ぐのはスクリプト注入であり、Cookie同意の問題は解決しません。サーバー側のログインとファイル権限については独立サイトのセキュリティ強化。
nonceとハッシュはどう使い分ける?固定のインラインスクリプトにはハッシュ、動的に生成されるインラインスクリプトにはnonceを使います。WordPressテーマ内の固定のトラッキングコードはハッシュで許可し、リクエストごとに内容が変わるスクリプトブロックにはnonceを使います。
配信場所にも工夫が必要です。CSPレスポンスヘッダーはNginxやCDN層で配信し、PHPアプリからの出力に頼らないようにしてください。アプリ層の出力はPHP障害時に失われますが、CDN層からの配信は安定しています。
| 配置場所 | 安定性 | 適用シーン |
|---|---|---|
| Nginx設定 | 高、アプリが落ちても残る | 自前サーバー |
| CDNルール | 高、全世界で有効 | Cloudflareなどを利用 |
| PHPアプリ出力 | 低、障害時に失われる | 一時的なテスト |
レポート受信エンドポイントも専用に用意してください。report-uriが指すアドレスをCSP自身がブロックすると、レポートを送信できなくなります。
独立系サイトでは既存のレポート収集サービスを使うか、Nginxに専用のlog endpointを設けて報告を受け取り、別のログファイルに書き出します。本体サイトのトラフィックログには残しません。
報告が増えたら集計が必要です。1日に数百件届くこともあり、1件ずつ見るのは無理なので、違反したディレクティブとブロックされたドメインでグループ化し、毎日新しく増えたドメインを重点的に確認します。
よくある誤解があります。CSPを入れたら安心だと思うことです。実際には、サードパーティスクリプトを追加するたび、テーマを更新するたびに、新しい違反元が入り込む可能性があります。
そのためEnforceに切り替えたあとも、report-uriは外せません。新しく追加したスクリプトのホワイトリスト登録を忘れたことに気づける唯一の信号源です。
ブラウザ対応にも触れておきます。モダンブラウザ(Chrome、Edge、Firefox、Safariの直近2年分)はCSP 3に完全対応しています。古いIEは無視して構いません。B2Bの独立系サイトの訪問者がIEを使うことはまずありません。
もう1つデプロイの注意点があります。CSPヘッダーはhttpとhttpsの両方に送る必要があります。httpsのみに送ると、httpでオリジンに戻る段階のリクエストがCSPの保護を受けません。
Enforceに切り替えたあとは、Report-Onlyヘッダーを並行してしばらく残すことをおすすめします。ブロックしつつ報告も受け取れるので、漏れがあってもレポートで確認できます。
CSPは一度きりのプロジェクトではありません。新しいサードパーティスクリプト(チャット、動画、広告など)を追加するたびに、まずレポートモードで違反として現れるのを確認し、該当ドメインをホワイトリストに追加してから本番に切り替えます。
CSPチェックを新規スクリプトのリリースチェックリストに入れましょう。スクリプトを追加すると同時にホワイトリストも追加し、リリース後にユーザーから「部品が表示されない」と報告されてからCSPを思い出す、という事態を避けます。
1つの判断基準があります。あるサードパーティスクリプトが外部ファイル化もハッシュ計算もできない場合、そのサードパーティを使う価値があるかを見直すべきです。unsafe-inlineに依存するサードパーティサービス自体が技術的負債です。
デプロイ完了後は、ブラウザのコンソールのSecurityパネルでページを1つずつ確認します。トップページ、商品ページ、ブログページ、フォームページをそれぞれ見て、赤い違反表示がないことを確認します。
確認で問題があったページは、レポートから該当ドメインを探し、ホワイトリストに追加してもう1日観察します。このサイクルを2〜3回繰り返せば安定します。
CSPは一度デプロイすれば終わりのセキュリティ項目ではありません。サードパーティスクリプトのライブラリとともに長期的に進化します。新しいスクリプトを追加するたびにホワイトリストも更新し、新たに現れたサードパーティスクリプトのドメインを登録し忘れないようにしてください。
| レポートの発生元 | 意味 | 対処 |
|---|---|---|
| script-src違反 | 新規スクリプトが未登録 | ドメイン追加か外部ファイル化 |
| img-src違反 | 画像の外部リンクが新しいドメイン | CDN追加か相対パスに変更 |
| frame-src違反 | 新しいiframeを埋め込み | 動画ドメインを追加か削除 |
| base-uri違反 | ページにbaseタグを注入 | すぐにXSSを調査 |
FAQ よくある質問
CSPは公開時から強制モードにすべき?
WordPressテーマのインラインスクリプトがCSPでブロックされたら?
CSPとHTTPS、HSTSはどう関係しますか?
CSPレポートが違反だらけになるのはなぜ?
CSPはSQLインジェクションを防げますか?
report-uriはまだ残す必要がありますか?
最後に
CSPの難しさはルールを書くことではなく、根気よくレポートモードを運用することにあります。2週間の観察期間で、その後数年のスクリプトを制御できるのですから、割に合います。
導入は3ステップ。まずReport-Onlyで14日間運用。レポートに従って実ドメインを1つずつホワイトリストに入れ、unsafe-inlineをハッシュで置き換える。違反が1桁まで減ったら強制に切り替え、同時にreportを残して監視を続けます。
覚えておきたい一言。CSPの安全度はホワイトリストがどれだけきれいかで決まります。unsafe-inlineやワイルドカードを残せば、この壁は築いていないのと同じです。
