本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
CSPによるXSS攻撃緩和策
ウェブアプリケーションにおけるクロスサイトスクリプティング(XSS)攻撃は、長年にわたり主要なセキュリティ脅威の一つであり続けています。Content Security Policy(CSP)は、このXSS攻撃を効果的に緩和するための強力なHTTPレスポンスヘッダーです。本記事では、セキュリティエンジニアの実務家向けに、CSPの仕組み、推奨される設定、および運用上の注意点について解説します。
脅威モデル:クロスサイトスクリプティング(XSS)
XSSは、攻撃者が悪意のあるスクリプトをウェブページに挿入し、そのスクリプトが他のユーザーのブラウザで実行されることによって発生する脆弱性です。成功した場合、攻撃者はユーザーのセッションハイジャック、Cookieの盗難、個人情報の窃取、ウェブサイトの改ざんなどを実行できます。XSS攻撃は主に以下の3種類に分類されます [2]。
Reflected XSS(反射型XSS): 攻撃者の入力がWebアプリケーションによって即座に、かつ検証されずにHTMLレスポンスに反映されることで発生します。フィッシングメールなどを通じて悪意のあるURLをクリックさせることで実行されます。
Stored XSS(格納型XSS): 攻撃者の入力がWebアプリケーションのデータベースなどに保存され、他のユーザーがその保存されたコンテンツを閲覧する際に悪意のあるスクリプトが実行されることで発生します。掲示板の投稿やコメント欄などが典型的な標的です。
DOM-based XSS(DOMベース型XSS): WebページのDOM(Document Object Model)を操作するクライアントサイドのスクリプトに脆弱性があり、ユーザーの入力が適切に処理されずにDOMに反映されることで発生します。サーバーサイドの処理を経由せず、ブラウザのみで完結する点が特徴です。
攻撃シナリオとCSPの役割
CSPが導入されていないウェブアプリケーションでは、以下のようなシナリオでXSS攻撃が成功する可能性があります。
攻撃者によるスクリプト挿入: 攻撃者は、入力フォームやURLパラメータなどを通じて、
<script>alert('XSS');</script>のような悪意のあるJavaScriptコードをアプリケーションに挿入します。サーバーによる脆弱なレスポンス: アプリケーションは、ユーザーからの入力値を適切にサニタイズ(無害化)せずに、そのままHTMLレスポンスに含めてブラウザに送信します。
ブラウザでのスクリプト実行: ユーザーのブラウザは、受け取ったHTMLを解析し、含まれる悪意のあるスクリプトを信頼されたコンテンツとして実行してしまいます。
CSPは、HTTPレスポンスヘッダーを通じて、ブラウザがどのオリジンからどのタイプのリソースを読み込むことを許可するかを厳密に定義することで、このスクリプト実行フェーズに介入します。例えば、CSPが「このページは自身のドメインからのみスクリプトを読み込む」と指示していれば、外部ドメインから挿入されたスクリプトはブロックされます。
CSPのバイパス手法も存在しますが、これらは主にCSPポリシーが不適切に設定されている場合に発生します [6]。例えば、'unsafe-inline'や'unsafe-eval'ディレクティブをポリシーに含めると、インラインスクリプトやeval()関数によるコード実行が許可され、XSS攻撃に利用される可能性があります。
検出と緩和策:効果的なCSPの導入
CSPは、ウェブサーバーがHTTPレスポンスヘッダーとしてContent-Security-Policyを送信することで機能します。このポリシーは、default-src、script-src、style-srcなどのディレクティブで構成され、それぞれ特定のリソースタイプ(スクリプト、スタイルシートなど)に対して許可されるオリジンを指定します [1]。
基本的なディレクティブと危険な設定
以下のディレクティブは、CSPポリシーの基本的な構成要素です [1]。
default-src: 他の特定のディレクティブが指定されていない場合のフォールバックポリシー。script-src: JavaScriptソースの許可オリジン。style-src: CSSスタイルシートの許可オリジン。img-src: 画像の許可オリジン。connect-src:XMLHttpRequest、WebSocketなどのネットワーク接続の許可オリジン。object-src:<object>、<embed>、<applet>タグの許可オリジン。base-uri:<base>タグで指定できるURLを制限。
避けるべきディレクティブ:
'unsafe-inline'はインラインスクリプトやスタイルを許可し、'unsafe-eval'はeval()のような文字列からコードを生成するメソッドを許可します。これらはXSS攻撃の経路となり得るため、セキュリティレベルを著しく低下させます [4]。
推奨されるCSP設定:NonceとStrict-Dynamic
最新のCSP推奨事項では、nonce(ナンバー・オン・スロープ)やhash、そしてstrict-dynamicを活用することで、より堅牢なポリシーを構築します [5]。
Nonce(ノンス): リクエストごとに一意のランダムな文字列(ノンス)を生成し、それを
script-srcディレクティブと、許可したい<script>タグのnonce属性の両方に含めます。これにより、ノンスを知らない攻撃者が挿入したスクリプトは実行されません。Hash(ハッシュ): 許可したいインラインスクリプトの内容のハッシュ値をCSPポリシーに含めます。コードが変更されるとハッシュ値も変わるため、改ざんを検出できます。
'strict-dynamic': ノンスまたはハッシュで許可されたスクリプトが、さらに信頼できるスクリプトを動的にロードすることを許可します。これにより、大規模なアプリケーションで多くのスクリプトタグにノンスを付与する手間を省きつつ、セキュリティを維持できます [5]。
安全な代替策のコード例
誤用例(推奨されないCSP):
unsafe-inlineやunsafe-evalはXSS脆弱性の温床となります。
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none';
このポリシーは、自身のオリジンからのスクリプトを許可しつつも、インラインスクリプトやeval()の使用も許可しているため、反射型XSSや格納型XSSに対して脆弱な状態です。攻撃者がどこか一点でもユーザー入力がHTMLに直接出力される箇所を見つければ、悪意のあるインラインスクリプトを実行できてしまいます。
安全な代替(推奨されるCSP):
ノンスとstrict-dynamicを組み合わせたポリシーは、最も強力なXSS緩和策の一つです。
Content-Security-Policy: script-src 'nonce-{RANDOM_NONCE}' 'strict-dynamic'; object-src 'none'; base-uri 'self'; report-uri /csp-report-endpoint;
このポリシーでは、script-srcに指定された'nonce-{RANDOM_NONCE}'と'strict-dynamic'がポイントです。{RANDOM_NONCE}はサーバーがリクエストごとに生成し、HTMLレスポンス内の<script>タグのnonce属性にも設定します。これにより、ノンスを持つスクリプトのみが実行され、さらにそれらのスクリプトが動的に他のスクリプトをロードすることも許可されるため、アプリケーションの機能性を損なわずにXSS攻撃を防ぎます。object-src 'none'はプラグイン(Flashなど)による脆弱性を排除し、base-uri 'self'は<base>タグの悪用を防ぎます。report-uriは違反レポートの送信先です。
ノンスの生成例(Python)
ノンスは予測不可能な値である必要があります。暗号学的に安全な乱数ジェネレーターを使用して、リクエストごとに新しいノンスを生成します。
import secrets
import base64
def generate_csp_nonce(length_bytes=16) -> str:
"""
Content Security Policy (CSP) nonceを生成する関数。
長さlength_bytes(デフォルト16バイト)のランダムなバイト列を生成し、
Base64 URL-safeでエンコードする。これにより、攻撃者が予測しにくい
一意のnonceが生成される。
入力:
length_bytes (int): 生成するバイト列の長さ。デフォルトは16。
出力:
str: Base64 URL-safeでエンコードされたnonce文字列。
前提:
secretsモジュールが利用可能であること。
計算量:
O(1) (固定長の乱数生成とエンコード)
メモリ条件:
O(1)
"""
nonce_bytes = secrets.token_bytes(length_bytes) # 例: 16バイトの乱数を生成
# Base64 URL-safeエンコードし、ASCII文字列としてデコード
nonce_base64 = base64.urlsafe_b64encode(nonce_bytes).decode('ascii')
return nonce_base64.rstrip('=') # Base64URLセーフパディングを除去
# 使用例 (Webフレームワーク内でリクエストごとに実行)
# current_nonce = generate_csp_nonce()
# HTTPレスポンスヘッダー: "Content-Security-Policy: script-src 'nonce-{current_nonce}' 'strict-dynamic';"
# HTMLのスクリプトタグ: <script nonce="{current_nonce}">...</script>
XSS攻撃チェーンとCSPによる介入
flowchart TD
A["攻撃者"] --> B{"悪意ある入力/スクリプト挿入"};
B --|SQLi/XSS経由| --> C["Webアプリケーション"];
C --|データ保存 (Stored XSS)| --> D["データベース"];
D --|出力時| --> C;
C --|脆弱なレスポンス生成| --> E["ユーザーのブラウザ"];
E --|CSPなしの場合| --> F["悪意あるスクリプト実行"];
F --|セッションハイジャック/情報漏洩など| --> A;
E --|CSP適用| --> G{"CSPポリシー評価"};
G --|許可されたリソースのみ| --> H["正当なスクリプト実行"];
G --|違反を検出| --> I["悪意あるスクリプトブロック"];
I --|レポート送信 (Report-URI)| --> J["CSPレポートエンドポイント"];
J --> K["セキュリティ監視/分析"];
上記の図は、XSS攻撃の流れとCSPがどのように介入して攻撃を緩和するかを示しています。攻撃者が挿入したスクリプトがアプリケーションの脆弱性を経由してユーザーのブラウザに到達しても、CSPが適用されていれば、ポリシーに違反するスクリプトは実行がブロックされ、攻撃が水際で阻止されます。
運用対策と現場の落とし穴
CSPは強力なツールですが、その導入と運用には注意が必要です。
鍵/秘匿情報の取り扱い
CSPにおいて「鍵/秘匿情報」とは、主にノンスの生成に用いられるランダムな値そのものです。ノンスはリクエストごとに一意である必要があり、予測不可能な値でなければなりません。これを実現するために、OSが提供する暗号学的に安全な乱数ジェネレーター(Pythonのsecretsモジュールなど)を使用することが不可欠です。
ローテーション: ノンスはリクエストごとに異なるため、厳密な意味でのローテーションは不要です。各リクエストで新しい一意のノンスが生成され、使い捨てされます。
最小権限: CSPポリシー自体は、許可するリソースのオリジンを最小限に絞ることで「最小権限の原則」を体現します。
監査:
report-uriまたはreport-toディレクティブを設定することで、CSP違反が発生した際にブラウザからレポートを受け取ることができます。これにより、潜在的なXSS攻撃の試みや、ポリシーの誤設定による正当なリソースのブロックを検知し、監査することが可能になります。これらのレポートを集約・分析する仕組み(例:Report-URI.comなどのサービスや自社構築のエンドポイント)を用意することが重要です。
導入時のフェーズとレポートモード
CSPをいきなり強制モードで導入すると、正当なスクリプトやリソースがブロックされ、アプリケーションの機能が停止する可能性があります。そのため、以下のフェーズでの導入が推奨されます [6]。
Content-Security-Policy-Report-Onlyモードでのテスト: このヘッダーを使用すると、ポリシー違反が発生してもスクリプトの実行はブロックされず、レポートのみが指定されたエンドポイントに送信されます。これにより、アプリケーションへの影響なしにポリシーの有効性を検証し、必要な調整を行うことができます。Content-Security-Policy-Report-Only: script-src 'nonce-{RANDOM_NONCE}' 'strict-dynamic'; object-src 'none'; report-uri /csp-report-endpoint;ポリシーの調整と移行: レポートを分析し、アプリケーションが必要とするすべてのリソースが許可されるようにポリシーを調整します。十分なテスト期間を経て問題がないことを確認したら、
Content-Security-Policyヘッダーに移行します。
現場の落とし穴
ポリシーの複雑化とメンテナンス負荷: 多様な外部サービスを利用しているアプリケーションでは、ポリシーが非常に複雑になりがちです。ポリシーが長すぎると、HTTPヘッダーのサイズ制限に抵触したり、可読性が低下してメンテナンスが困難になる可能性があります。定期的な見直しと整理が必要です。
誤検知と検出遅延:
Report-Onlyモードでのテストが不十分だと、本番環境移行後に正当な機能がブロックされる「誤検知」が発生し、可用性に影響を与えます。また、報告された違反レポートの分析が遅れると、攻撃の兆候を見逃す「検出遅延」につながります。不完全なXSS緩和: CSPはXSS緩和の強力な手段ですが、万能ではありません。DOM-based XSSの一部は、CSPだけでは完全に防ぎきれない場合があります。また、JSONPエンドポイントの悪用や、CSPヘッダー自体に注入される反射型XSSなど、バイパス手法も存在します。サーバーサイドでの厳格な入力検証、出力エンコーディング、セキュアなJavaScriptコーディング(例:信頼できない文字列からのHTML生成を避ける)を組み合わせることが不可欠です。
WAF/IDSとの連携: CSPはクライアントサイドでの保護を提供しますが、Web Application Firewall (WAF) や Intrusion Detection System (IDS) はサーバーサイドでの保護を提供します。これらを組み合わせることで、多層防御を実現し、より広範な攻撃に対応できます。
まとめ
Content Security Policy (CSP) は、XSS攻撃を緩和するための非常に効果的なメカニズムです。特にnonceとstrict-dynamicを組み合わせた現代的なアプローチは、アプリケーションの機能性を維持しつつ、高いセキュリティレベルを提供します。
しかし、CSPの導入と運用には、Report-Onlyモードでの慎重なテスト、継続的なポリシーの監視と更新、そして他のセキュリティ対策との組み合わせが不可欠です。開発者とセキュリティ担当者が連携し、最新のベストプラクティスに従うことで、ウェブアプリケーションをXSSの脅威から保護し、ユーザーに安全な体験を提供することができます。
参照情報:
[1] Mozilla Contributors. “Content Security Policy (CSP)”. MDN Web Docs, 2024年5月28日更新. https://developer.mozilla.org/ja/docs/Web/HTTP/CSP
[2] OWASP. “Cross-site Scripting (XSS)”. OWASP, 2023年9月23日更新. https://owasp.org/www-community/attacks/xss/
[3] Mozilla Contributors. “CSP: script-src”. MDN Web Docs, 2024年5月28日更新. https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
[4] Google. “Mitigating XSS with CSP”. Google Developers, 2024年1月19日更新. https://developer.chrome.com/docs/lighthouse/best-practices/csp-xss/
[5] Google. “CSP strict-dynamic”. Google Developers, 2024年1月19日更新. https://developer.chrome.com/docs/web/fundamentals/security/csp/#strict-dynamic
[6] OWASP. “Content Security Policy Cheat Sheet”. OWASP Cheat Sheet Series, 2024年1月2日更新. https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html

