現場で泥をすすりながらシステムを守り抜いているエンジニア諸君、ご苦労様。
「XSS対策はサニタイズしていれば大丈夫」なんて言葉、2024年の今となっては寝言に等しい。最近の攻撃者は、モダンフレームワークの裏をかいたり、ブラウザの仕様を悪用して、いとも簡単に権限を奪取してくる。
今日は、対症療法としての「エスケープ」ではなく、「ブラウザに強制的なルールを突きつける」ことでXSSを根絶する、CSP(Content-Security-Policy)の実践的な設計論を叩き込む。
—
なぜ「サニタイズ」だけでは足りないのか?
反射型・格納型・DOM型。種類を並べればキリがないが、攻撃者の目的はいつも同じだ。「ブラウザ上で意図しないスクリプトを動かすこと」。
開発現場でよくあるのは、のような単純なペイロードを弾くことに執心し、肝心の「信頼できないスクリプトが混入した際に、ブラウザがどう振る舞うべきか」というセキュリティポリシーを定義していないことだ。これが最大の盲点。
CSPは、この「何が実行を許され、何がブロックされるべきか」をブラウザに直接指示する最強の防壁だ。
—
厳格なCSPの実装:nonce(ナンス)の活用
CSPの設計で最もやってはいけないのが、unsafe-inline を許可することだ。これを入れた瞬間にCSPはザルになる。
信頼できるスクリプトだけを通すための切り札が nonce だ。リクエストごとに使い捨てのランダムな文字列を生成し、サーバー側でスクリプトタグに付与する。これ以外のインラインスクリプトは、ブラウザが問答無用で実行を拒否する。
実装例:PHP + CSPヘッダー
まずは、リクエストごとに生成したnonceをヘッダーに乗せ、スクリプトにも付与する実装を見てほしい。
—
運用で死なないための「Reporting API」
厳格なポリシーを適用すると、既存のサードパーティ製ライブラリやタグマネージャーが「突然動かなくなった」というトラブルが必ず起きる。そこで導入すべきが report-to または report-uri だ。
いきなり Content-Security-Policy を適用するのではなく、最初は Content-Security-Policy-Report-Only で運用を開始し、違反ログを収集しろ。
Nginxでの設定例
CSPの違反をJSONで受け取るエンドポイントを設定
add_header Content-Security-Policy-Report-Only “default-src ‘self’; script-src ‘nonce-random123’; report-uri /csp-violation-report-endpoint”;
このレポートをSentryや自前のログサーバーに飛ばし、「何がブロックされたか」を分析してから本番の Content-Security-Policy へ切り替える。これが、ユーザー体験を損なわずにセキュリティを最大化するプロの作法だ。
—
攻撃者視点:なぜこれが「詰み」なのか?
攻撃者がデータベースに悪意のあるスクリプトを格納できたとしても、そのスクリプトには『現在のリクエストで生成された未知のnonce』が付与されていない。
ブラウザは、「お前のタグには正しいnonceがない。よって実行しない」と判断する。これがDOM型XSSであろうと、格納型XSSであろうと関係ない。「実行エンジンそのもの」を制御下に入れているからこそ、小手先のペイロードは無力化される。
—
今すぐやるべきチェックリスト
1. unsafe-inline の削除: 既存のコードを修正してでも、nonceまたはhashによる制御へ移行する。
2. base-uri 'none' の設定: タグによるリソースの乗っ取りを確実に防ぐ。
3. object-src 'none': Flashなどのレガシーなプラグインを悪用した攻撃を物理的に遮断する。
4. CSP Reportの監視: 違反ログを放置せず、開発チームのKPIに「CSP違反ゼロ」を組み込む。
セキュリティとは、境界線を引くことだ。曖昧な「たぶん大丈夫」という性善説に基づいたコードは捨てろ。ブラウザというクライアント側のエンジンに対し、冷徹かつ論理的な制約を押し付ける。これこそが、我々エンジニアに求められる「安全な開発」の真髄だ。
分かったら、さっそく自分の担当するアプリケーションのヘッダーを確認してこい。脆弱性は待ってくれないぞ。
コメント