CSPは「最後の砦」じゃない、フロントエンドの「制約付き安全圏」だ
現場でコードをレビューしていると、「CSPを設定しました。これでXSSは大丈夫ですよね?」という質問をよく受ける。正直に言おう。CSPは万能薬ではない。
XSSの根本原因は、ユーザー入力を適切にエスケープせず、ブラウザに「実行可能なコード」として解釈させてしまうことにある。CSPは、万が一その「脆弱なコード」が混入してしまった際に、ブラウザ側で「おい、そのスクリプトは許可リストにないぞ!」と遮断するための、いわばフロントエンドの防波堤だ。
今回は、形骸化したCSP設定ではなく、攻撃者が最も嫌がる「インラインスクリプト全禁止」を前提とした、実務で使える最強のCSP構成を叩き込む。
—
1. なぜ「インラインスクリプト」が諸悪の根源なのか
攻撃者がSQLインジェクションやOSコマンドインジェクションを仕込むのと同様、XSSの攻撃者はHTMLの中に悪意あるタグを注入する。
このコードが注入されたとき、CSPが緩いとブラウザは無防備に実行してしまう。これを防ぐための鉄則はただ一つ。unsafe-inline を絶対に許可しないことだ。
—
2. 実務で採用すべき「厳格なCSP」設定
Nginxでヘッダーを送出する場合の設定例だ。これをベースに、自社の要件に合わせてホワイトリストを絞り込んでほしい。
Nginx設定ファイル (server または location ブロック内)
add_header Content-Security-Policy ”
default-src ‘self’;
script-src ‘self’ https://trusted-cdn.com;
object-src ‘none’;
base-uri ‘self’;
frame-ancestors ‘none’;
form-action ‘self’;
” always;
この設定のポイント(なぜこれが強いのか)
default-src 'self': 全ての読み込み元を自ドメインに制限する。script-src 'self' https://trusted-cdn.com: インラインスクリプトを一切認めず、外部JSも許可したCDN以外からは読み込ませない。object-src 'none': Flash等のプラグインによる脆弱性を排除する。frame-ancestors 'none': クリックジャッキング攻撃を防ぐために、自サイトをiframe等で埋め込むことを禁じる。form-action 'self': フォームの送信先を自サイトのみに限定し、外部へのデータ流出を防ぐ。
—
3. 「どうしてもインラインスクリプトが必要な場合」の次善の策
レガシーなシステム改修で、どうしてもインラインスクリプトを即時排除できない場合があるだろう。その際は unsafe-inline を使うのではなく、「Nonce(ナンス)」を採用してほしい。
Nonceとは、リクエストごとに生成する「一度きりの使い捨てトークン」だ。
実装例(PHPでのNonce生成)
'strict-dynamic' を併用することで、信頼されたスクリプトが動的に読み込んだ子スクリプトまで信頼対象に含めることができる。これがモダンな開発現場における「現実的な妥協点」だ。
—
4. 運用上の「泥臭い」Tips
CSPをいきなり厳格に導入すると、既存のトラッキングツールやライブラリが軒並み死んで画面が真っ白になる。現場でインシデントを起こさないための手順はこれだ。
1. Content-Security-Policy-Report-Only で開始せよ
- 本番環境に投入する前に、
Report-Onlyヘッダーを使って、どのリソースがブロックされるかを監視する。 report-uriまたはreport-toディレクティブを使って、レポートを収集するエンドポイントを指定する。
2. サードパーティスクリプトを「見直す」口実にする
- CSPでブロックされた古い計測タグは、この機会に削除するか、GTM(Google Tag Manager)のような管理ツールへ移行する判断材料にする。
3. 定期的な監査
- 開発者が便利なライブラリを勝手に追加して
script-srcが肥大化していないか。四半期に一度は必ずヘッダーのホワイトリストを見直すこと。
最後に:セキュリティは「諦め」の積み重ねだ
「全部許可する」のは楽だが、それはセキュリティではない。「どこまでなら制限しても業務が回るか」を追求し、権限を最小化し続けること。それが我々エンジニアの仕事だ。
CSPの設定は、アプリケーションの「健全性」を示す指標でもある。もし自社のCSPヘッダーが unsafe-inline で埋め尽くされているなら、それはまだ「守れていない」のと同じだ。今日、今すぐ、一行ずつでも制限を強めていこう。それが、君の書いたコードを、そしてユーザーのデータを守ることにつながる。
コメント