【実務・中級編】Content-Security-Policy (CSP) の厳格な設定とディレクティブの最適化 – アプリケーションセキュリティ & 安全な開発防御ガイド

CSPは「最後の砦」じゃない。侵入された後の「終わりの始まり」を止める外科手術だ

現場でコードを叩いていると、「CSP(Content-Security-Policy)なんて設定しても画面が崩れるだけでしょ?」とぼやくエンジニアによく出会う。確かに、レガシーなJSが混在する環境で厳格なCSPを適用するのは、稼働中の心臓を止めてバイパス手術をするようなものだ。

だが、忘れないでほしい。XSS(クロスサイトスクリプティング)は今や単なる「アラートボックスを出す」遊びじゃない。セッションハイジャック、クレカ情報の抜き取り、そしてバックドアの設置まで、攻撃者は君のフロントエンドを「自分の庭」のように操る。

今日は、教科書的な説明はすっ飛ばして、実務で明日から使える「攻撃者に隙を見せないCSP」の設計論を叩き込む。

—

1. なぜ「雑なCSP」は意味がないのか

よくある失敗例がこれだ。

Content-Security-Policy: default-src ‘self’ https://trusted-cdn.com; script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’;

この設定、'unsafe-inline' と 'unsafe-eval' が入っている時点で、「玄関の鍵はかけたけど、窓は開けっ放しです」と言っているのと同じだ。攻撃者がインジェクションに成功すれば、このポリシーは無力化される。

攻撃者の視点:PoCの恐怖

もし君のアプリにを注入できたら? 'unsafe-inline'が許可されていれば、CSPは何も警告を出さずに実行を許可する。攻撃者は、君が苦労して設定したポリシーの隙間を縫って、ブラウザ上の特権を奪取するんだ。

—

2. 脱・unsafe!「Nonce」を用いたモダンな制御

インラインスクリプトを一切許さないのは理想だが、現実的には難しい。そこで使うのが Nonce(Number used once) だ。リクエストごとに使い捨てのランダムな文字列を生成し、許可されたスクリプトにのみそのタグを付与する。

実装例:Python (Flask) でのセキュアな動的Nonce生成

サーバーサイドで生成し、CSPヘッダーとHTMLに埋め込む。

import secrets
from flask import Flask, render_template, make_response

app = Flask(__name__)

@app.after_request
def add_csp(response):
# 1. リクエストごとに暗号学的に安全なNonceを生成
nonce = secrets.token_urlsafe(16)
# 2. CSPヘッダーにnonceをセット
response.headers[‘Content-Security-Policy’] = (
f”default-src ‘self’; ”
f”script-src ‘nonce-{nonce}’ ‘strict-dynamic’; ” # strict-dynamicはモダンブラウザ向け
f”object-src ‘none’; ”
f”frame-ancestors ‘none’;” # クリックジャッキング対策
)
# 3. テンプレート側にnonceを渡す
return response

テンプレート (jinja2) での使用例

—

3. インフラレベルでの防御:Nginxによるヘッダー制御

アプリケーションコードを汚したくない場合は、Nginxでヘッダーを注入するのも手だ。ただし、Nonceのような動的値は扱えないため、hash を使うか、インラインスクリプトを外部ファイル化する設計が前提となる。

Nginx設定例:

server {
# クリックジャッキング対策とあわせて設定
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ https://trusted-scripts.com; object-src ‘none’; base-uri ‘self’; frame-ancestors ‘none’;”;
add_header X-Frame-Options “DENY” always;
add_header X-Content-Type-Options “nosniff” always;
}

—

4. 現場で「壊さない」ための運用ロードマップ

いきなり厳格な設定を投入して本番環境を真っ白にするのは、セキュリティチーフとしては失格だ。以下の手順で進めるのが鉄則だ。

1. Content-Security-Policy-Report-Only を使う:
いきなり制限をかけるのではなく、違反をブラウザからレポートさせるモードで運用する。
report-uri または report-to を指定し、Sentry等の監視ツールに飛ばして「どのスクリプトがブロックされたか」を可視化せよ。

2. 外部スクリプトの棚卸し:
「昔入れて放置した分析ツール」や「使っていないCDN」を容赦なく消せ。script-srcにホワイトリストが増えるほど、攻撃の踏み台が増える。

3. strict-dynamic の活用:
モダンブラウザであれば strict-dynamic を活用しろ。これは「信頼されたスクリプトが動的に読み込んだスクリプトも信頼する」という設計で、複雑な依存関係を持つJSライブラリ環境下でもCSPを適用しやすくする。

—

最後に:セキュリティは「諦めない」こと

CSPは万能薬じゃない。だが、攻撃者がサーバーに潜り込み、悪意あるスクリプトを仕込もうとした時、「ブラウザ側でそのスクリプトが実行できない」という事実は、被害を最小限に抑える最強の防御壁になる。

「動けばいい」というコードから、「守られるべくして守られている」コードへ。後輩諸君、君たちが書くその一行が、ユーザーの情報を守る最後の砦になることを忘れないでほしい。

さて、次は frame-ancestors を使ったクリックジャッキング対策について深掘りしようか。準備はいいか?

コメント

タイトルとURLをコピーしました