【実務・中級編】 Content-Security-Policy(CSP)によるクライアントサイド攻撃の防御 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

「CSPは面倒くさい」を卒業する。XSSを無力化する現代的な防御戦略

現場でインシデント対応をしていると、いまだに「XSSなんてバリデーションとエスケープを徹底すれば防げる」という言葉を耳にする。それは正しい。正しいが、我々エンジニアは往々にしてミスをする。テンプレートエンジンの設定漏れ、ライブラリの脆弱性、あるいは複雑すぎるフロントエンドのロジック。

「人間はミスをする」という前提でシステムを組むのが、プロの仕事だ。

そのための最強の防波堤が Content-Security-Policy (CSP) だ。今回は、ただの「お守り」ではなく、現代の攻撃者が最も嫌がる「強固なCSP」の実装術を伝授する。

—

なぜ従来のCSPは「ザル」なのか

かつて推奨されていた script-src 'self' https://trusted.cdn.com のような設定は、現代のWebアプリケーションでは既に限界を迎えている。

攻撃者は、JSONPエンドポイントの悪用や、data: スキーム、あるいはCDN上に放置された古いライブラリの脆弱性を突いてくる。これらをすべて許可リストに入れると、リストは肥大化し、最終的にはメンテナンス不能になって「設定を緩める」という愚行に走ることになる。

我々が目指すべきは、「暗号学的に安全なランダム値を要求する」というアプローチだ。

—

現代のスタンダード:nonceとstrict-dynamic

最も現実的で、かつ突破困難なのが nonce (Number used once) を使ったCSPだ。

実装のコンセプト

1. サーバーサイドで、リクエストごとに推測不可能なランダム文字列(nonce)を生成する。
2. 信頼できる <script> タグにその nonce を付与する。
3. CSPヘッダーで、その nonce を持たないインラインスクリプトを一切禁止する。

これに strict-dynamic を組み合わせることで、信頼されたスクリプトが動的に読み込むサブスクリプトも自動的に許可されるため、現代的なJSエコシステムとも共存できる。

実装例:PHPで生成するnonce

<?php
// 1. セキュアなランダム値を生成(暗号論的に安全な乱数)
$nonce = base64_encode(random_bytes(16));

// 2. CSPヘッダーを送信
// 'strict-dynamic'は、nonce付きのスクリプトから読み込まれるスクリプトも許可する
// 'unsafe-inline'は、nonce非対応ブラウザのフォールバック用(nonceがあれば無視される)
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic' https: http:; object-src 'none'; base-uri 'none';");
?>

<!DOCTYPE html>
<html>
<head>
    <!-- 3. nonceを付与してスクリプトを読み込む -->
    <script nonce="<?php echo $nonce; ?>" src="/js/app.js"></script>
</head>
<body>
    <!-- 攻撃者が注入しても、nonceがないため実行されない -->
    <script>alert('XSS!');</script>
</body>
</html>

—

インフラ層での適用(Nginxの場合)

アプリケーション側でヘッダーを制御できないレガシーな環境や、一元管理したい場合は、Nginxでベースラインを固めるのが定石だ。ただし、nonceは動的である必要があるため、静的なポリシーを適用する場合は unsafe-inline を排除することを最優先にする。

# /etc/nginx/conf.d/security.conf
# 厳格なポリシーの例(nonceなしのインラインスクリプトを完全に遮断)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self';" always;

# frame-ancestors 'none' はクリックジャッキング対策として必須

—

攻撃者はここを狙っている:盲点への対策

どれほど完璧に見えるCSPでも、以下の設定が漏れていると攻撃の足がかりにされる。

1. object-src 'none' を忘れない:
これを指定しないと、Flashや古いプラグインを介したコード実行を許す可能性がある。現代においてプラグインが必要なサイトはほぼないはずだ。
2. base-uri 'none' または 'self':
これを設定しないと、攻撃者は <base> タグを注入し、相対パスのスクリプト読み込み先を自分のサーバーに書き換えてしまう。
3. frame-ancestors 'none':
XSSとは別枠だが、UIを乗っ取られないために重要だ。

—

まとめ:運用を止めるな

CSPを導入して最初に直面するのは「画面が真っ白になる」という事態だ。開発中のコンソールにはエラーが溢れるだろう。

最初は Content-Security-Policy-Report-Only ヘッダーを使うことを強く勧める。これを使えば、ブロックせずに違反だけをレポートできる。

  • ステップ1: Report-Only モードで運用し、違反レポートを収集する。
  • ステップ2: 必要なスクリプトをすべて特定し、nonceを組み込む。
  • ステップ3: 全ての違反が解消されたことを確認してから、本番モードへ切り替える。

セキュリティは「導入して終わり」ではない。strict-dynamic を活用したこの防御モデルは、今日の攻撃手法に対する最強の盾となる。面倒くさがらず、今日から実装を始めてほしい。君たちのコードが、誰かの盾になることを願っている。

コメント

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