CSPは「気休め」か「最後の砦」か ― 現役レッドチーマーが語るXSS防御のリアル
現場でペネトレーションテストをしていると、開発者が「XSS対策はちゃんとやってますよ、htmlspecialchars()を通しているので」と自信満々に言う場面によく出くわす。だが、我々攻撃者からすれば、それは「玄関の鍵はかけたけど、窓は全開です」と言っているようなものだ。
XSSの脆弱性は、1箇所でも見落としがあればそこからすべてが崩壊する。そんな時、ブラウザ側で不正なスクリプトの実行を強制終了させる最強の防波堤が Content-Security-Policy (CSP) だ。今日は、単なる「設定の羅列」ではなく、攻撃者がどうやってCSPを回避しようとし、どうすればそれを「鉄壁」にできるのか、実務的な知見を共有する。
—
1. 攻撃者が狙う「CSPの盲点」
攻撃者は、CSPが設定されているからといって諦めることはない。特に狙われるのは、設定の「緩さ」だ。
unsafe-inlineの罠:script-src 'unsafe-inline'が許可されている場合、攻撃者はタグ内に直接alert(document.cookie)を埋め込むだけで、CSPは無力化される。- ホストの不備:
script-src https://trusted.example.comと設定していても、もしそのドメイン上にJSONPエンドポイントや、アップロード可能な画像ホスティング機能があれば、それらを経由して任意のJSを実行される。 - Nonceの使い回し: サーバーサイドで生成する
nonce(使い捨てトークン)が推測可能、あるいは静的ファイルに固定で埋め込まれている場合、攻撃者はそれを悪用してスクリプトを注入する。
我々レッドチームは、常に「信頼できるドメインの中に、攻撃者が制御できるJavaScriptを置く場所はないか?」という視点で探索している。
—
2. 実践:最強のCSPポリシーを実装する
「コピペで動く」ことだけを目的とせず、今のモダンなWebアプリケーションで最低限守るべき「厳格なCSP」を設定しよう。
Nginxでの設定例
Nginxのレスポンスヘッダーで設定するのが最も効率的だ。
# Nginxの設定ファイル内 (http, server, locationブロック)
# 厳格なCSPポリシーの適用
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'nonce-random12345';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
block-all-mixed-content;
upgrade-insecure-requests;
" always;
この設定のポイント:
default-src 'self': 基本的に同一ドメイン以外からの読み込みを全拒否。script-src 'nonce-...': インラインスクリプトを全面的に禁止し、許可されたスクリプトのみをnonceで実行させる。object-src 'none': 古いFlashやプラグインによるエクスプロイトを完全に遮断。frame-ancestors 'none': クリックジャッキング攻撃を防御。
—
3. アプリケーション側での実装(PHPの例)
CSPを有効に活用するには、動的に生成する nonce をスクリプトタグに付与する必要がある。
<?php
// 1. サーバー側で一時的な乱数を生成
$nonce = base64_encode(random_bytes(16));
// 2. CSPヘッダーに埋め込んでブラウザに送信
header("Content-Security-Policy: script-src 'nonce-$nonce'; object-src 'none';");
?>
<!-- 3. HTML側ではこのnonceを持つスクリプトのみが実行される -->
<script nonce="<?php echo htmlspecialchars($nonce); ?>">
console.log("このスクリプトは安全に実行されます");
</script>
<!-- 攻撃者が注入しようとする <script>alert(1)</script> はnonceがないためブロックされる -->
—
4. 現場で生き残るための運用Tips
CSPは、いきなり厳格に設定するとサイトが壊れる可能性がある。以下のステップで導入するのがプロのやり方だ。
1. Report-Onlyモードで開始:
まずは Content-Security-Policy-Report-Only ヘッダーを使って、どのスクリプトがブロックされるかをコンソールで確認する。
2. インラインスクリプトを排除する:
JavaScriptをHTML内に直書きするのをやめ、すべて外部ファイル化し、nonce を付与する設計に移行する。
3. WAFとの併用:
CSPはブラウザ側の防御だが、WAF(AWS WAFやCloudflareなど)で不正なリクエストパターンを事前に遮断することで、多層防御を構築する。
最後に
セキュリティは「パズル」だ。CSPというピースをはめ込むことで、攻撃者が入り込める隙間を極限まで小さくできる。面倒な設定に思えるかもしれないが、インシデント対応で深夜に叩き起こされることを考えれば、この数行の記述は「最高の投資」になるはずだ。
次は、あなたのプロジェクトのNginx設定を確認してみてほしい。unsafe-inline の文字が見えたなら、それは今すぐ書き換えるべき「穴」だ。健闘を祈る。
コメント