CSPは「最後の砦」じゃない。XSSを無力化する最強の盾だ。
いいか、よく聞け。現場で「XSS対策はサニタイズしてるから大丈夫」なんて言ってるエンジニアほど、俺がインシデント対応で駆けつける現場の火種を作っている。HTMLエスケープやバリデーションは「一次防衛」に過ぎない。開発者のポカ一つで、その防御壁は一瞬で崩れる。
そこで登場するのが Content-Security-Policy (CSP) だ。これはブラウザという「クライアント側の実行環境」に対して、「お前が読み込んでいいのはこのドメインのスクリプトだけだ。それ以外は動かすな」と命令する強力なルールブックだ。
今日は、小手先の対策ではなく、ブラウザをガチガチに縛り上げるための「実務で使えるCSP設計」を伝授する。
—
なぜインラインスクリプトとeval()が諸悪の根源なのか
攻撃者が狙うのは、脆弱性を見つけた瞬間に注入する のようなコードだ。これがなぜ動くのか?それはブラウザが「HTMLに書かれたスクリプト」をデフォルトで信頼しているからだ。
CSPで script-src 'self' を設定すれば、外部ファイル(.js)しか読み込めなくなる。つまり、攻撃者がどれだけ画面にタグを埋め込んでも、外部サーバーから自分の悪意あるJSを読み込ませる場所がない限り、何も実行できない。これが、インジェクション攻撃の息の根を止めるということだ。
—
実践:最強のCSPヘッダーを構築する
まずは、Nginxの設定でCSPを送り出す方法を見てみよう。これ一つで、ほとんどのXSS攻撃は無力化される。
Nginx設定例 (nginx.conf)
CSPヘッダーの設定
‘self’: 自ドメインのみ許可
‘unsafe-inline’: 原則禁止(これが一番重要)
‘unsafe-eval’: 原則禁止(eval()系を殺す)
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’; base-uri ‘self’; frame-ancestors ‘none’;” always;
default-src 'self': 全てのコンテンツを自ドメインからのみ許可する。script-src 'self': スクリプトは自ドメインのファイルのみ。インラインスクリプトはこれで全滅する。object-src 'none': Flash等の古いプラグイン実行を完全に禁止。frame-ancestors 'none': クリックジャッキング対策。自サイトを他サイトのiframeに埋め込ませない。
—
「でも、どうしてもインラインスクリプトが必要な時は?」
業務アプリで「ReactやVueのビルド生成物でインラインスクリプトがどうしても残ってしまう」というケースはあるだろう。その場合、安易に 'unsafe-inline' を許可してはいけない。「Nonce(ナンス)」を使え。
PHPでのNonce実装例
Nonceとは、リクエストごとに生成される「使い捨ての暗号キー」だ。
この実装なら、ブラウザは「nonceが一致するscriptタグだけを実行する」という厳格な挙動に切り替わる。攻撃者がHTMLを書き換えても、正しいnonceを知らなければスクリプトは沈黙する。
—
運用時の注意点:レポートバックを忘れるな
CSPをいきなりガチガチに設定すると、動いていたはずの機能が軒並み死ぬ。まずは Content-Security-Policy-Report-Only ヘッダーを使い、ブラウザが拒否した通信を監視するログを収集しろ。
ブロックはせず、違反報告だけを特定のURLに飛ばす
Content-Security-Policy-Report-Only: default-src ‘self’; report-uri /csp-violation-report-endpoint;
ここで「どの機能が動かなくなったか」を精査し、ホワイトリストを調整する。この泥臭いチューニングをサボるかどうかが、プロとアマの分かれ道だ。
—
最後に:セキュリティは「設定」で終わらない
CSPは素晴らしいツールだが、魔法ではない。設定した後に「じゃあ、このアプリにSQLi脆弱性はあるか?」「CSRF対策はどうだ?」と、多層的な防衛を考えるのがエンジニアの仕事だ。
いいか、セキュリティに「完璧」はない。あるのは「攻撃コストをどれだけ跳ね上げたか」という結果だけだ。CSPを導入して、攻撃者が「このサイト、突破するのに手間がかかりすぎるな」と思ってターゲットから外す状況を作る。それこそが、我々が目指すべきゴールだ。
現場で何か行き詰まったら、いつでも聞け。手を動かせ。それが最強の防御術だ。
コメント