【入門】Webサイトの「防犯カメラと警備員」:Content-Security-Policy(CSP)で悪質なスクリプトを確実に弾く方法
みなさん、こんにちは!日々の開発や運用、本当にお疲れ様です。
Webアプリケーションのセキュリティ対策と聞くと、まずは「パスワードを複雑にする」「通信をHTTPSで暗号化する(AESや楕円曲線暗号などを使う)」といった施策が思い浮かびますよね。これらは例えるなら、「家の玄関に頑丈な鍵をかけ、配送伝票を暗号解読不能な特殊な文字で書く」ようなものです。
しかし、もし「泥棒が宅配業者を装って堂々と玄関から入ってきて、家の中に盗聴器を仕掛けていったら」どうでしょうか?どれだけ鍵が頑丈でも、家の中に侵入された後では防ぎようがありませんよね。
Webの世界でこれと同じことが起きるのが、悪名高きXSS(クロスサイトスクリンプティング)攻撃です。そして、家の中に侵入した泥棒(悪質なスクリプト)が好き放題動くのを阻止する「最強の室内警備員」こそが、今回ご紹介する Content-Security-Policy(CSP) です。
「CSPって設定が複雑そう…」「間違えるとサイトが動かなくなりそうで怖い…」と思っている初心者の開発者やIT担当者の方も大丈夫です!一歩ずつ、防犯の仕組みに例えながら分かりやすく解説していきますね。
—
1. なぜ暗号化だけでは足りないのか?XSS攻撃の恐ろしさ
セキュリティの基礎として「共通鍵暗号(AES)」や「公開鍵暗号(RSAや楕円曲線暗号 ECC)」による通信の暗号化(HTTPS)は絶対に不可欠です。これによって、途中のネットワークで通信内容を盗み見られるリスクはほぼゼロになります。
しかし、攻撃者は「通信の横取り」が無理だと分かると、「ユーザーのブラウザ上でJavaScriptを実行させる」という作戦に切り替えます。
家の防犯で例えると…
- HTTPS(暗号化):手紙を金庫に入れて鍵をかけて送る仕組み。途中で盗み見られません。
- XSS攻撃:手紙の中身に「この紙を読んだら、家の金庫を開けて中身を犯人の家に郵送しなさい」という悪質な命令文(スクリプト)を忍び込ませる攻撃です。
ユーザーのブラウザがこの命令文を「正しい指示だ」と信じて実行してしまうと、Cookieに保存されたログイン情報が盗まれたり、勝手に別の口座にお金を送金されたりしてしまいます。
この「悪質な命令文(スクリプト)の実行」を根本から阻止するために登場するのが CSP(コンテンツ・セキュリティ・ポリシー) です。
—
2. CSPは「館内の立ち入り許可証&持ち込み検査ルール」
CSPを一言で言うと、Webサーバーからブラウザに対して発行する「このページでは、誰が書いたどんなコードなら実行して良いか」を指定する指示書(HTTPレスポンスヘッダー)です。
家の防犯に例えるなら、「我が家に入ってもいい業者一覧リスト」と「持ち込み禁止物ルール」を玄関の警備員に渡しておくようなものです。
- 「スクリプトは、うちのサーバーにあるファイル以外は一切実行禁止!」
- 「HTMLの中に直接書かれた正体不明のメモ(インラインスクリプト)は無視して!」
- 「Flashなどの古い怪しい仕組み(
<object>タグ)は持ち込み禁止!」
このように厳格なルールをブラウザに命令することで、万が一HTMLの中に悪意あるコードが注入されても、ブラウザが「あ、このコードはルール違反だから実行しないよ!」とブロックしてくれるのです。
—
3. これだけは押さえたい!CSPの必須ディレクティブ
CSPにはたくさんの設定項目(ディレクティブ)がありますが、初心者の皆さんがまず絶対に覚えるべき重要なルールは以下の3つです。
1. default-src : 基本の制限ルール(すべての資材のデフォルト)
2. script-src : JavaScriptの読み込み・実行ルール(最重要!)
3. object-src : プラグイン(Flashなど)の読み込みルール
それぞれの意味を詳しく見ていきましょう。
① default-src 'none' (基本はすべて立ち入り禁止)
防犯の基本は「原則立ち入り禁止、許可されたものだけを通す(ホワイトリスト方式)」です。
最初に default-src 'none' と設定することで、画像もスタイルシートもスクリプトも、一旦すべてブロックする状態にします。ここから必要なものだけを個別に許可していくのが、最も安全な設計(最小権限の原則)になります。
② script-src (JavaScriptの実行許可ルール)
攻撃者が狙う最大のターゲットがJavaScriptです。script-src を使って、「どの場所にあるスクリプトなら実行してよいか」を厳格に制限します。
ここで最も重要なのが、インラインスクリプト(HTML内に直接書かれた <script>alert('XSS')</script> のようなコード)を禁止することです。
③ object-src 'none' (古い危険な仕掛けの全面禁止)
昔のWebサイトでよく使われていたFlashやJavaアプレットなどを読み込む <object>、<embed>、<applet> タグは、セキュリティ上の脆弱性の宝庫でした。現代のWeb開発ではこれらはほぼ不要ですので、迷わず 'none'(全面禁止)に設定します。
—
4. 現場で使える!「厳格なCSP」の実践コード例
それでは、実際のWebサーバーやHTMLでどのようにCSPを設定するのか、実用的なサンプルコードを見てみましょう!
今回は、現代のセキュリティ運用で推奨されている「Nonce(ノンス)値を使った厳格なCSP(Strict CSP)」の例をご紹介します。
「Nonce(ノンス)」ってなに?
Nonceとは「一回限りの合言葉」のことです。
サーバーがページを表示するたびに、ランダムで予測不能な暗号文字列(合言葉)を生成します。そして、「この合言葉を知っているスクリプトだけ実行を許可するよ」という仕組みです。これなら、攻撃者が後から注入した悪意あるスクリプトには合言葉が付いていないため、絶対に実行されません!
—
設定例:Webサーバー(PHP)でのレスポンスヘッダー出力
まずは、サーバー側でランダムな合言葉(Nonce)を生成し、CSPヘッダーとして送信する処理です。
<?php
// 1. 暗号学的に安全なランダムな文字列(Nonce)を生成します(24バイトのランダム値をBase64エンコード)
$nonce = base64_encode(random_bytes(24));
// 2. 厳格なCSPヘッダーを組み立てます
// - default-src 'self': 基本的には自分のサーバーのファイルのみ許可
// - script-src 'nonce-...': 生成した合言葉を持つスクリプトのみ許可
// - object-src 'none': Flashなどの危険なオブジェクトは全面禁止
// - base-uri 'none': <base>タグによるURL書き換え攻撃を防止
$cspHeader = "Content-Security-Policy: " .
"default-src 'self'; " .
"script-src 'nonce-" . $nonce . "' 'strict-dynamic'; " .
"object-src 'none'; " .
"base-uri 'none';";
// 3. ブラウザに向けてヘッダーを送信します
header($cspHeader);
?>
—
設定例:HTML側でのスクリプト記述
次に、HTML側で先ほど生成した「合言葉(Nonce)」をスクリプトタグに付与します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>安全なWebページ</title>
</head>
<body>
<h1>CSPで守られたWebサイトへようこそ!</h1>
<!--
合言葉(nonce)が付いているため、このインラインスクリプトは安全に実行されます。
※PHP側で出力した $nonce の値を設定します。
-->
<script nonce="<?php echo $nonce; ?>">
console.log("このスクリプトは正しい合言葉を持与されているため実行されます!");
</script>
<!--
仮に攻撃者がXSS脆弱性を突いて、以下のようなコードを埋め込んできたとします。
しかし、合言葉(nonce)が存在しない(または合わない)ため、ブラウザが即座にブロックします!
-->
<!-- <script>alert('攻撃成功!Cookieを盗みます');</script> ← これは動かない! -->
</body>
</html>
Apache サーバーの設定ファイル(.htaccess 等)で設定する場合
PHPなどの動的言語を使わない静的サイトの場合でも、以下のように .htaccess に記述してヘッダーを付与できます(外部スクリプトのドメインを指定して許可するアプローチです)。
# .htaccess によるCSPヘッダーの設定例
<IfModule mod_headers.c>
# 信頼できるドメイン(自サーバーとGoogle Analyticsなど)のみスクリプト実行を許可
# object-src は全面ブロック ('none')
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.google-analytics.com; object-src 'none'; base-uri 'none';"
</IfModule>
—
5. 実務での運用のコツ:いきなり遮断して画面が真っ白に!?を心配するあなたへ
「CSPの凄さは分かったけれど、設定を間違えて既存のWebサイトが動かなくなったらどうしよう…」と不安になりますよね。実は、プロの現場でもいきなり厳格なCSPを適用することは稀です。
安全に導入するために、以下の 「2ステップ運用」 を行いましょう!
ステップ1:Report-Only モードで事前テストをする
いきなりブロックするのではなく、「違反があったら報告だけして、スクリプトの実行自体は許可する」 というテスト用ヘッダーが存在します。それが Content-Security-Policy-Report-Only です。
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'nonce-xxx'; object-src 'none'; report-uri /csp-report-endpoint
このヘッダーをつけておくと、動かなくなるリスクゼロで、「どのスクリプトがブロック対象になるか」のログを収集できます。予期せぬ外部ツール(チャットボットや解析ツールなど)がブロックされていないか事前に確認できるわけですね!
ステップ2:ログを確認して問題を修正したら、本番ヘッダーへ切り替える
Report-Only でエラーが出なくなったら、満を持して Content-Security-Policy ヘッダーに切り替えます。これで「強固な警備システム」の稼働開始です!
—
まとめ:一歩ずつ安全なWebサイトを目指しましょう!
今回は、Webサイトの「室内警備員」である Content-Security-Policy(CSP) の原理と実践的な導入方法について解説しました。内容を振り返ってみましょう。
1. 暗号化(HTTPS) は通信を守る「玄関の鍵」。でも XSS は「家の中での暴走」。
2. CSP はブラウザに渡す「立ち入り許可ルール」。悪意あるコードの暴走を止める!
3. object-src 'none' で古い危険な機能を排除。
4. script-src + Nonce(合言葉) で、許可したスクリプトだけを動かす!
5. 導入時は Report-Only モード でテストすれば怖くない。
セキュリティ対策は、一度にすべてを完璧にする必要はありません。「まずは object-src 'none' を設定してみる」「Report-Only でログを見てみる」といった小さな一歩からで大丈夫です。
知識を武器にして、攻撃者が手を出せない安全で信頼されるWebサイトを、一歩ずつ一緒に作っていきましょうね!応援しています!
コメント