【実務・中級編】 Content-Security-Policy (CSP) の厳格な設定とバイパス手法 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

CSPは「魔法の杖」ではない:現場で叩き込まれるバイパスの現実

「Content-Security-Policy (CSP) を入れたから、うちのアプリはXSSに対して無敵だ」。もし君のチームでそんな楽観的な声が聞こえてきたら、即座に目を覚まさせてやってくれ。

CSPは強力な防御層だが、設定を誤れば「ただの気休め」に成り下がる。特に、開発者が利便性を優先して unsafe-inline や unsafe-eval を安易に許可した瞬間、セキュリティの壁には巨大な穴が開く。今日は、教科書的な説明はすっ飛ばして、我々攻撃側が「どうやってその壁をすり抜けるか」、そして「どうすれば実務で鉄壁の防御を築けるか」を深掘りする。

—

1. 攻撃者が狙うCSPの「盲点」

攻撃者は、CSPが定義されているからといって諦めない。むしろ、定義されているからこそ「その枠組みの中での抜け道」を探す。

JSONPによるバイパス

古くからある手法だが、今でも非常に強力だ。もしCSPで script-src に信頼できるドメイン(例:https://trusted.example.com)が許可されており、そのドメイン上に古いJSONPエンドポイントが残っていれば、それを利用して任意のスクリプトを実行できる。

例えば、https://trusted.example.com/api?callback=alert(1) のようなURLを <script src="..."> で読み込めば、CSPの許可リストを堂々とパスできる。これが「信頼できるドメインなら安全」という神話が崩壊する瞬間だ。

ライブラリの「二面性」

CDNから読み込んでいる有名なJavaScriptライブラリの中には、DOM操作やテンプレート機能を持つものがある。攻撃者は、たとえスクリプトの注入ができなくても、ライブラリの特定の機能を使って「DOMベースのXSS」を引き起こす。これを防ぐには、単なるドメイン制限ではなく、nonce(ナンス)による制御が不可欠だ。

—

2. 鉄壁を築く:nonceとstrict-dynamicの実装

「なんとなく設定する」のは今日で終わりにしよう。現代のWebセキュリティにおいて、最も堅牢なのは nonce を用いたホワイトリスト方式だ。

PHPによるnonceの実装例

リクエストごとに一意のランダムな値を生成し、それをヘッダーとHTMLタグの両方に埋め込む。

<?php
// リクエストごとに暗号学的に安全なnonceを生成
$nonce = base64_encode(random_bytes(16));

// CSPヘッダーの送信
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic'; object-src 'none'; base-uri 'none';");
?>

<!-- HTML側での利用 -->
<!-- このnonceと一致しないスクリプトは、インラインであっても実行されない -->
<script nonce="<?php echo $nonce; ?>">
    console.log("このスクリプトは実行される");
</script>

ここで重要なのは 'strict-dynamic' だ。これを指定することで、nonceが付与されたスクリプトから動的に読み込まれる子スクリプトも自動的に許可される。これにより、複雑なライブラリ構成でもスムーズに動作させつつ、攻撃者の差し込む怪しいスクリプトを遮断できる。

—

3. インフラレベルでの設定(Nginx)

アプリケーションコードだけで防御するのは限界がある。Nginx等のWebサーバー側で、デフォルトのポリシーを強制するのがプロの流儀だ。

# /etc/nginx/conf.d/security.conf
# 厳格なポリシーをヘッダーに注入
add_header Content-Security-Policy "default-src 'self'; script-src 'nonce-RANDOM_NONCE_VALUE' 'strict-dynamic'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';";

# セキュリティのための追加ヘッダー
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;

※ RANDOM_NONCE_VALUE の部分は、実際にはバックエンド側で差し替える必要がある。

—

4. 現場で守るべき「3つの鉄則」

現場の泥臭い運用の中で、これだけは守り抜いてほしい。

1. unsafe-inline は絶対禁止: これを許可した時点でXSS防御の9割は死ぬ。nonce への移行を最優先タスクにしろ。
2. object-src 'none' を忘れるな: Flashや古いプラグインを動かす必要が現代にあるか? ないなら迷わず none だ。これだけで object タグ経由の攻撃を完全に遮断できる。
3. report-uri / report-to を活用せよ: CSP違反が発生した際、その通知を受け取るエンドポイントを必ず設定する。攻撃の予兆を検知する唯一のセンサーだ。

違反レポートを受け取るための設定例 (CSPヘッダー)

Content-Security-Policy: ...; report-uri /csp-violation-report-endpoint;

—

最後に:セキュリティは「動的なプロセス」だ

CSPの設定を一度決めて満足するエンジニアは、次のインシデントの主役になる。Web技術の進化とともに、攻撃手法も常に変化している。

「本当にこのCSP設定で防御できているのか?」
定期的に Report-Only モードで運用し、違反ログを分析し、不要な権限を削ぎ落としていく。その地道な作業こそが、我々が「プロ」として信頼されるための唯一の道だ。

さあ、今すぐ自社のCSPヘッダーを確認してくれ。unsafe-inline の文字が見えたら、それが今日の君の最初の仕事だ。

コメント

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