XSSの「最後の砦」:CSP(Content Security Policy)をただの飾りで終わらせないための設計術
現場でコードレビューをしていると、未だに「XSS対策は入力値のエスケープだけで完結している」と信じているエンジニアに出くわす。だが、現実は甘くない。DOM型XSSや、ライブラリの脆弱性を突いた意図しないスクリプトの実行は、入力値をどれだけ厳密にサニタイズしていてもすり抜けてくることがある。
そこで重要になるのが、ブラウザという「最後の防衛線」を制御するContent-Security-Policy (CSP)だ。今回は、ただ設定するだけでなく、インフラエンジニアが泣かない、かつ攻撃者を絶望させるためのCSP設計術を叩き込む。
—
1. 攻撃者が狙う「盲点」:なぜCSPが必要なのか
反射型や格納型はエスケープで防げるかもしれない。しかし、攻撃者が「信頼されたソース」を装って外部から悪意あるスクリプトを読み込ませる手法(例えば、CDNの汚染や、管理画面の脆弱性を突いたスクリプト埋め込み)に対しては、アプリケーションコード側での制御は限界がある。
攻撃のPoC(概念実証)の例:
攻撃者は、あなたが「信頼できる」と設定しているサードパーティのCDNの脆弱性や、単なるのようなインジェクションを狙う。この時、CSPがなければブラウザは素直にそのスクリプトを実行してしまう。これを防ぐのがCSPの役割だ。
—
2. 「コピペで終わらせない」セキュアなCSP設計
よくある失敗は、unsafe-inlineやunsafe-evalを気軽に使ってしまうことだ。これを入れた瞬間に、CSPは「警告を出すだけのただのログ出力装置」へと成り下がる。
以下の構成は、現代のWebアプリケーションにおいて推奨される「モダンかつ堅牢な」CSPのベースラインだ。
Nginxでのヘッダー設定例
インフラ層で制御することで、アプリケーションの変更に左右されずに強制力を担保する。
Nginxの設定ファイル
add_header Content-Security-Policy ”
default-src ‘self’;
script-src ‘self’ https://trusted-apis.com;
object-src ‘none’;
base-uri ‘self’;
frame-ancestors ‘none’;
upgrade-insecure-requests;
” always;
default-src 'self': 基本的に自ドメイン以外からの読み込みを全て禁止する。script-src 'self' ...: スクリプトの実行元を制限。'unsafe-inline'は絶対に記述しない。object-src 'none': Flash等の古いプラグイン実行を完全に封殺する。frame-ancestors 'none': クリックジャッキング対策。サイトをiframe内に埋め込ませない。
—
3. 実装の壁:インラインスクリプトをどう扱うか?
「インラインスクリプト(HTML内に直接書かれた)を消すのが難しい」という声はよく聞く。その場合、Nonce(一時的な乱数)を用いるのがプロの選択だ。
Python (Flask) でのNonce実装例
サーバー側で毎回ユニークな値を生成し、HTMLとCSPヘッダーの両方に埋め込む手法だ。
import secrets
from flask import Flask, render_template, Response
app = Flask(__name__)
@app.after_request
def add_csp(response):
# リクエストごとに安全な乱数を生成
nonce = secrets.token_hex(16)
# CSPヘッダーにnonceを追加
response.headers[‘Content-Security-Policy’] = f”script-src ‘nonce-{nonce}’ ‘strict-dynamic’;”
# テンプレートに渡す
return response
テンプレート側(Jinja2)での記述
この手法を使えば、攻撃者が埋め込んだ悪意あるスクリプトには「正しいnonce」が含まれていないため、ブラウザは即座に実行を拒否する。これが、現在考えうる最も強力なXSS防御の一つだ。
—
4. 運用上の鉄則:まずは「レポートモード」から始めよ
いきなり本番環境に厳格なCSPを適用すると、正当な機能までブロックされてサイトが崩壊するリスクがある。まずは Content-Security-Policy-Report-Only ヘッダーを使って、どのポリシーが違反を引き起こしているかをログで確認することから始めよう。
違反をブロックせず、指定したエンドポイントにJSONでレポートを送る
add_header Content-Security-Policy-Report-Only “default-src ‘self’; report-uri /csp-violation-report-endpoint/”;
このログを監視することで、「あ、このライブラリはCDNからインラインスクリプトを注入しているのか」といった、開発チームさえ気づいていなかった技術的負債が可視化される。
最後に:セキュリティは「設定」ではなく「文化」
CSPは強力なツールだが、これさえあれば100%安全という魔法ではない。根本的な脆弱性はコードを書いて修正し、CSPは万が一の穴を塞ぐための「最後の安全装置」として位置づけること。
もし、今日このブログを読んだなら、まずは現在運用しているシステムのHTTPヘッダーを確認してほしい。CSPの設定がないか、あってもunsafe-inlineが放置されていないだろうか?
それが、あなたのシステムを狙う攻撃者に対する、最初の一撃になるはずだ。
コメント