CSP(コンテンツセキュリティポリシー)とは?XSS防止の仕組み・nonce/hash・GA・GTMへの影響を徹底解説|サイバーセキュリティ.com

CSP(コンテンツセキュリティポリシー)とは?XSS防止の仕組み・nonce/hash・GA・GTMへの影響を徹底解説



「XSS攻撃を防ぐためにCSPを導入したい」「nonce・hashって何?unsafe-inlineは使っていいの?」——Webセキュリティを強化しようとする開発者・担当者から多く寄せられる疑問です。

CSP(Content Security Policy:コンテンツセキュリティポリシー)とは、Webサーバーがブラウザに対して「このページではどこからコンテンツを読み込んでよいか」を指定するセキュリティ機能です。HTTPレスポンスヘッダーとして送信され、主にXSS(クロスサイトスクリプティング)攻撃の防止に使われます。

本記事では、CSPの仕組み・主要ディレクティブ・nonce/hashによる厳格なCSPの実装・テスト方法・落とし穴まで実践的に解説します。

CSPとは?

CSP(Content Security Policy)とは、Webサイトのサーバーがブラウザに対して「このページでは何を読み込んでよいか」を指示するセキュリティ機能です。HTTPレスポンスヘッダーとして送信され、ブラウザはその指示に従ってコンテンツの読み込みや実行を制限します。

 「入場リスト(ホワイトリスト)」のたとえ
CSPはクラブの入場リストのようなものです。リストに載っていないゲスト(スクリプト・画像・スタイルなど)は入場できません(読み込まれない)。攻撃者が悪意あるスクリプトを注入できたとしても、そのスクリプトはリストに載っていないためブラウザが実行を拒否します。

CSPの目的と重要性

CSPの主な目的はXSS(クロスサイトスクリプティング)攻撃への対策です。XSS攻撃は、攻撃者が悪意あるスクリプトをWebページに挿入し、ユーザーのCookie・セッション・個人情報を窃取する攻撃です。

重要:CSPは入力サニタイズの代替ではない
CSPはサニタイズが失敗した場合の「最後の防衛線」です。CSPを設定していても、入力値のサニタイズ・エスケープ処理は必ず実施してください。多層防御の一つとして位置づけることが重要です。

CSPの設定方法

CSPはHTTPレスポンスヘッダーとして送信します。

Content-Security-Policy: default-src ‘self’; img-src ‘self’ example.com; script-src ‘self’ https://trusted.com

または、HTMLの<meta>タグでも設定できます(一部機能は非対応)。

<meta http-equiv=”Content-Security-Policy” content=”default-src ‘self'”>

ポリシーはセミコロンで区切られた複数の「ディレクティブ」で構成されます。

XSS攻撃とCSPの防御メカニズム

CSPが防ぐXSSの攻撃パターン

XSS攻撃では以下のような悪意あるコードがページに注入されます。CSPはこれらすべてをブロックできます。

攻撃パターン コード例 CSPの防御方法
外部スクリプトの注入 <script src=”https://evil.example/hack.js”></script> script-srcで許可ドメインを制限
インラインスクリプト <script>悪意あるコード</script> インラインスクリプトをデフォルトでブロック
インラインイベントハンドラー <img onmouseover=”攻撃コード”> インラインイベントハンドラーをブロック
javascript: URL <a href=”javascript:攻撃コード”> javascript: URLをブロック
eval()などの危険なAPI eval(“攻撃コード”) unsafe-evalを禁止してeval()を無効化

主要ディレクティブと設定方法

フェッチディレクティブ(リソースの読み込み制御)

ディレクティブ 対象リソース 設定例
default-src 他のディレクティブで未指定のすべて default-src ‘self’
script-src JavaScriptファイル script-src ‘self’ https://cdn.example.com
style-src CSSスタイルシート style-src ‘self’ https://fonts.googleapis.com
img-src 画像ファイル img-src ‘self’ data: https://images.example.com
font-src Webフォント font-src ‘self’ https://fonts.gstatic.com
connect-src fetch・XHR・WebSocketなど connect-src ‘self’ https://api.example.com
object-src Flash・Javaなどのプラグイン object-src ‘none’(ほぼ常に’none’推奨)

その他の重要なディレクティブ

  • frame-ancestors:このページを<iframe>で埋め込むことを許可するドメインを指定。クリックジャッキング対策として’none’または’self’を推奨
  • base-uri:<base>要素で使用できるURLを制限。’none’または’self’を指定することで、<base>タグを使ったURLの書き換え攻撃を防ぐ
  • upgrade-insecure-requests:HTTPリソースのリクエストをHTTPSに自動アップグレードする
  • report-to:CSP違反のレポート送信先エンドポイントを指定(report-uriは非推奨)

ソース式の種類

ソース式 意味
‘none’ すべてをブロック
‘self’ 同じオリジン(ドメイン・プロトコル・ポート)のみ許可
https://example.com 指定ドメインからの読み込みを許可
*.example.com example.comのすべてのサブドメインを許可
https: HTTPSスキームのすべてのリソースを許可
‘unsafe-inline’ インラインスクリプト・スタイルを許可(非推奨
‘unsafe-eval’ eval()などの危険なAPIを許可(非推奨
‘unsafe-inline’は使わないこと
‘unsafe-inline’を使用すると、CSPのXSS防御機能がほぼ無効になります。インラインスクリプトが必要な場合はnonce(後述)を使ってください。

厳格なCSP:nonce・hashによる実装

ホワイトリストベース(URLリスト形式)のCSPは設定が難しく、誤って安全でないドメインを許可してしまうリスクがあります。現在の推奨はnonce(ノンス)またはhash(ハッシュ)を使った「厳格なCSP」です。

nonceを使った実装

nonceとは、リクエストごとにサーバーが生成するランダムな値です。スクリプトにnonce属性を付与し、CSPヘッダーに同じ値を記載することで、そのスクリプトのみ実行を許可します。

サーバー側(Node.js/Expressの例):
const crypto = require(‘crypto’);app.get(‘/’, (req, res) => {
const nonce = crypto.randomUUID();
res.setHeader(
‘Content-Security-Policy’,
`script-src ‘nonce-${nonce}’; object-src ‘none’; base-uri ‘none’`
);
res.send(`
<script nonce=”${nonce}” src=”/main.js”></script>
<script nonce=”${nonce}”>console.log(“安全なインラインスクリプト”);</script>
`);
});

nonceを使う際の重要なルール

  • nonceはリクエストごとに必ずランダムな新しい値を生成する(再利用禁止)
  • nonceを予測されないよう十分な長さのランダム値を使う(crypto.randomUUID()推奨)
  • サーバーが攻撃者注入のスクリプトに誤ってnonceを付与しないよう注意する

hashを使った実装

スクリプトの内容をハッシュ化し、そのハッシュ値をCSPヘッダーに記載する方法です。動的なnoneが使えない静的サイトやSPAに適しています。

ハッシュ値の計算(Node.js):
const crypto = require(‘crypto’);
const scriptContent = ‘console.log(“hello!”)’;
const hash = crypto.createHash(‘sha256’).update(scriptContent).digest(‘base64’);
// → 使用するCSPヘッダー:
// Content-Security-Policy: script-src ‘sha256-ハッシュ値’

スクリプトの内容が変わるたびにハッシュを再計算する必要がある点がnonceとの違いです。

strict-dynamicキーワード

サードパーティスクリプトが動的に追加のスクリプトを読み込む場合、nonceやhashだけでは対応できません。strict-dynamicを追加すると、nonceまたはhashで信頼されたスクリプトが動的に読み込むスクリプトも信頼されます。

Content-Security-Policy: script-src ‘nonce-{RANDOM}’ ‘strict-dynamic’; object-src ‘none’; base-uri ‘none’;
strict-dynamicの注意点
strict-dynamicを使うと、信頼されたスクリプトが読み込む子スクリプトも自動的に信頼されます。信頼されたスクリプト自体がXSSの脆弱性を持つ場合、CSPで保護できなくなるリスクがあります。

厳格なCSPの推奨設定

Google等が推奨する「厳格なCSP」の設定例です。

nonceベース(動的サイト推奨):
Content-Security-Policy:
script-src ‘nonce-{RANDOM}’ ‘strict-dynamic’;
object-src ‘none’;
base-uri ‘none’;hashベース(静的サイト・SPA推奨):
Content-Security-Policy:
script-src ‘sha256-{HASHED_SCRIPT}’ ‘strict-dynamic’;
object-src ‘none’;
base-uri ‘none’;

CSPの段階的な導入方法

いきなり厳格なCSPを本番環境に適用するとサイトが壊れる可能性があります。Content-Security-Policy-Report-Onlyヘッダーを使うと、実際にはブロックせずに違反をレポートとして収集できます。

段階的な導入手順:

①まずReport-Onlyモードで動作させる
Content-Security-Policy-Report-Only: default-src ‘self’; report-to csp-endpoint

②違反レポートを分析してポリシーを調整する
Reporting-Endpoints: csp-endpoint=”https://example.com/csp-reports”

③問題がなければ本番のCSPヘッダーに切り替える
Content-Security-Policy: default-src ‘self’;

違反レポートの形式

CSP違反が発生すると、ブラウザは以下のようなJSONをPOST送信します。

{
“body”: {
“blockedURL”: “https://evil.example/hack.js”,
“effectiveDirective”: “script-src-elem”,
“documentURL”: “https://example.com/page”,
“originalPolicy”: “default-src ‘self'”
},
“type”: “csp-violation”
}

クリックジャッキング対策

frame-ancestorsディレクティブは、このページを<iframe>で埋め込むことを許可するドメインを制御します。クリックジャッキング攻撃への有効な対策です。

# すべての埋め込みを禁止(推奨)
Content-Security-Policy: frame-ancestors ‘none’# 同一オリジンのみ許可
Content-Security-Policy: frame-ancestors ‘self’
frame-ancestorsはX-Frame-Optionsヘッダーをより柔軟に置き換えます。新しいサイトではframe-ancestorsの使用を推奨します。

CSPと組み合わせるべき他のセキュリティヘッダー

ヘッダー 目的 推奨設定例
Strict-Transport-Security (HSTS) HTTPSの強制 max-age=31536000; includeSubDomains
X-Content-Type-Options MIMEタイプスニッフィングの防止 nosniff
Referrer-Policy Referrerヘッダーの制御 strict-origin-when-cross-origin
Permissions-Policy ブラウザ機能(カメラ・位置情報等)の制御 camera=(), microphone=(), geolocation=()

GA・広告タグ・GTMへの影響と対処法

CSPが厳しく設定されていると、Google Analytics・Google Ads・Yahoo!広告・Meta広告などの計測タグが正常に動作しなくなることがあります。これらのタグは外部ドメインからJavaScriptを読み込んで動作するためです。

CSPによる計測への影響例

  • GA4のイベント計測が全く行われない
  • 広告のコンバージョンタグが発火せずコンバージョンが計測されない
  • リマーケティングリストが収集できない
  • ヒートマップツール・A/Bテストツールが動作しない

CSPが設定されているか確認する方法

①ブラウザの開発者ツールで確認

  1. F12キーで開発者ツールを開く
  2. 「Network」タブを選択してページを再読み込み
  3. 一番上のドキュメントをクリック→「Headers」タブを開く
  4. 「content-security-policy」ヘッダーがあればCSPが設定されている

②Consoleエラーで確認(タグ実装後に問題が発生した場合)

CSPによってブロックされたリソースがある場合、Consoleに以下のようなエラーが表示されます。

Content Security Policy: The page’s settings blocked the loading of a resource at https://www.googletagmanager.com/gtag/js (“script-src”)

エラーメッセージには「どのリソースが」「どのディレクティブで」ブロックされたかが記載されています。この情報を開発者・サーバー管理者に共有してCSPの修正を依頼します。

GA・広告タグに必要なCSP設定の追加方法

計測タグを正しく動作させるには、必要なドメインをCSPに追加してもらいます。主なディレクティブと追加すべきドメインの例は以下の通りです。

ディレクティブ 追加が必要なドメイン例(GA4・GTM)
script-src www.googletagmanager.com、www.google-analytics.com
connect-src www.google-analytics.com、analytics.google.com、stats.g.doubleclick.net
img-src www.googletagmanager.com、www.google-analytics.com
設定例(GA4+GTMを許可する場合):
Content-Security-Policy: default-src ‘self’; script-src ‘self’ www.googletagmanager.com www.google-analytics.com; connect-src ‘self’ www.google-analytics.com analytics.google.com; img-src ‘self’ www.googletagmanager.com;

GTM(Googleタグマネージャー)使用時の注意点

GTMを許可するだけでは不十分です。GTM経由で配信される各タグがさらに別のドメインにアクセスするため、それらのドメインもCSPへの追加が必要です。GTMを許可するとまた別のドメインのエラーが出る、という「イタチごっこ」が発生します。

GTMのCSP対応の進め方

  • まずCSPをReport-Onlyモードで動作させてエラーを収集する
  • GTMプレビューモード・開発者ツールのConsoleでブロックされたドメインを確認する
  • 必要なドメインを一つずつCSPに追加してテストする
  • Googleの公式ヘルプページ「コンテンツセキュリティポリシーが設定されているページでタグマネージャーを使用する」を参照する
CSP設定変更はマーケターが直接行わない
CSPの設定変更はWebサーバーやインフラの根幹に関わります。マーケターが直接変更するのではなく、必要なドメインのリストと理由を開発者・サーバー管理者に共有して対応を依頼してください。変更後は必ずGA4のDebugViewやGTMプレビューモードで計測が正常に動作することを確認します。

CSPに関連する被害・注意事例2選

事例1:XSS攻撃によりカード情報が漏えいした事例

株式会社ファインエイドでは、同社が運営する「健康いきいきライフスタイル」に対するサイバー攻撃により、5,193名のクレジットカード情報や個人情報が漏えいした可能性が公表されました。攻撃者は、サイト内の脆弱性を悪用し、閲覧者に不正なスクリプトを実行させるクロスサイトスクリプティング(XSS)攻撃によって、決済用フォームを改ざんしていました。

CSPは、このようなXSS攻撃の被害を軽減するための有効な追加対策です。script-srcで許可するスクリプトの読み込み元を制限し、connect-srcで外部への通信先を制御することで、攻撃者が注入したスクリプトの実行や情報送信を抑止できる可能性があります。ただし、CSPは入力値の検証やエスケープ処理の代替ではなく、多層防御の一つとして導入することが重要です。

事例2:ふるさと納税特設サイトでXSS攻撃が確認された事例

鹿児島県志布志市が運営する「志布志市ふるさと納税特設サイト」では、何者かによる不正アクセスを受け、クレジットカード情報910件が流出した可能性が公表されました。外部専門機関の調査により、サイト内の脆弱性を利用したクロスサイトスクリプティング(XSS)攻撃が仕掛けられていたことが判明しています。

このような事例では、入力値のサニタイズやエスケープ処理に加えて、CSPを設定しておくことで、想定外の外部スクリプトの読み込みや不審な通信を制限できる可能性があります。特に決済フォームや会員情報入力ページでは、script-srcconnect-srcframe-ancestorsなどを適切に設定し、XSSやクリックジャッキングへの多層防御を行うことが重要です。

よくある質問(FAQ)

Q. ‘unsafe-inline’を使わずにインラインスクリプトを使う方法は?

はい、nonce(ノンス)またはhash(ハッシュ)を使います。nonceはリクエストごとにランダムな値をサーバーで生成し、CSPヘッダーとスクリプトの両方に同じ値を設定します。hashはスクリプトの内容をSHA-256などでハッシュ化した値をCSPに記載します。どちらもインラインスクリプトを特定して許可できるため、’unsafe-inline’より安全です。

Q. CSPを設定するとサイトが壊れることは?

あります。特に外部CDNのスクリプト・インラインスタイル・Google Analyticsなどのサードパーティタグが動作しなくなるケースがよくあります。Content-Security-Policy-Report-Onlyヘッダーを使って段階的に導入し、違反レポートで問題を把握してからポリシーを本番適用することを強く推奨します。

Q. script-srcに’self’だけ設定すれば十分?

XSS対策としては不十分な場合があります。’self’ではインラインスクリプトは依然としてブロックされますが、同じオリジン上にあるスクリプトはすべて許可されます。JSONP脆弱性のある自サーバー上のエンドポイントが悪用されるリスクも残ります。nonceまたはhashを使った厳格なCSPへの移行が推奨されます。

Q. インラインイベントハンドラー(onclick等)はどう対処すればいいですか?

インラインイベントハンドラーはnonceやhashでも許可できず、CSPで完全にブロックされます。addEventListener()を使った外部スクリプトへの書き換えが必要です。例:onclick="..." → JavaScriptでelement.addEventListener('click', ...)に置き換えてください。

Q. frame-ancestorsとX-Frame-Optionsの違いは何?

機能は同じ(iframeへの埋め込み制御)ですが、frame-ancestorsの方がより柔軟です。X-Frame-Optionsは「DENY」「SAMEORIGIN」「ALLOW-FROM(ブラウザによって非対応)」の3択だけですが、frame-ancestorsは複数のドメインを指定できます。新しいサイトではframe-ancestorsの使用を推奨します。

まとめ

CSP(コンテンツセキュリティポリシー)は、Webサーバーがブラウザに対して許可するコンテンツの読み込み元を指示するセキュリティ機能で、XSS攻撃の防止に特に有効です。HTTPレスポンスヘッダーとして送信され、インラインスクリプトのブロック・外部スクリプトの制御・eval()などの危険なAPIの無効化が可能です。

現在の推奨実装は、ホワイトリスト型ではなくnonceまたはhashを使った「厳格なCSP」です。object-src 'none'base-uri 'none'と組み合わせることで最も効果的なXSS防御が実現できます。’unsafe-inline’は絶対に使用しないでください。

導入は必ずContent-Security-Policy-Report-Onlyで段階的に行い、違反レポートを分析してポリシーを最適化してから本番に適用することで、サイトを壊さずに安全に展開できます。CSPはHSTS・X-Content-Type-Options・Permissions-Policyなど他のセキュリティヘッダーと組み合わせることで、より強固な多層防御を実現できます。

SNSでもご購読できます。