Featured image of post XSSとCSRFの本質的な違いとモダンな防御策

XSSとCSRFの本質的な違いとモダンな防御策

Webアプリケーションの2大脆弱性はいかにして悪用されるか。

はじめに

現代のWebアプリケーションにおいて、セキュリティは単なる追加機能ではなく、システムの基盤を成す最も重要な要素の一つです。その中でも、XSS (Cross-Site Scripting) と CSRF (Cross-Site Request Forgery) は、歴史が古く、かつ未だに多くのWebアプリケーションで発見される深刻な脆弱性です。これらはしばしば混同されがちですが、攻撃のメカニズムも、それに対する防御策も根本的に異なります。

この記事では、XSSとCSRFの本質的な違いを解き明かし、攻撃者がどのようにこれらの脆弱性を悪用するのか、そして開発者が実装すべきモダンな防御策について、歴史的な変遷を交えながら詳細に解説します。


1. XSS (Cross-Site Scripting) の深層

XSSは、攻撃者がWebページに悪意のあるスクリプト(主にJavaScript)を注入し、そのページを閲覧した他のユーザーのブラウザ上でそのスクリプトを実行させる攻撃手法です。この攻撃の本質は、「信頼されていないデータが、適切な処理を経ずに実行可能なコードとして解釈されてしまうこと」にあります。

XSSの3つの主要なタイプ

XSSは、データが保存・反射される経路と、危険な解釈が起きる場所に注目して説明できます。以下の分類は完全に排他的ではなく、StoredまたはReflectedのデータがクライアント側で危険に処理される場合もあります。

1. Stored XSS (蓄積型XSS)

Stored XSSは、複数の閲覧者に被害を広げる可能性があるタイプです。攻撃者が送信した悪意のあるスクリプトが、データベースやファイルシステムなどのサーバー側に保存(蓄積)されます。その後、正規のユーザーがそのデータを含むページを閲覧した際に、保存されていたスクリプトがブラウザに送信され、実行されます。実際の深刻度は、実行される権限や利用状況によって異なります。

  • 典型的な発生箇所: コメント欄、掲示板、ユーザープロフィール、レビュー機能など。
  • 脅威: 影響範囲が非常に広く、ページを開いたすべてのユーザーが被害に遭う可能性があります。

2. Reflected XSS (反射型XSS)

Reflected XSSは、悪意のあるスクリプトがサーバーに保存されることなく、リクエストの一部(URLパラメータやフォームデータなど)として送信され、サーバーからのレスポンスにそのまま「反射」して含まれることで発生します。

  • 典型的な発生箇所: 検索結果ページ、エラーメッセージの表示、ステップ間のデータ受け渡しなど。
  • 攻撃手法: 攻撃者は、悪意のあるパラメータを含むURLをユーザーにクリックさせる(フィッシングメールやSNSを利用)ことで攻撃を成立させます。

3. DOM-based XSS

DOM-based XSSは、クライアントサイド(ブラウザ上)のJavaScriptが、信頼できないデータを危険なシンクで処理することで発生します。データがサーバーを経由する場合もあり、分類の要点は危険な処理がクライアント側で起きることです。

  • メカニズム: アプリケーションのJavaScriptが、window.location や document.referrer などの攻撃者が制御可能なソースからデータを読み取り、それを innerHTML や eval() などの危険なシンク(実行ポイント)に直接渡してしまうことで発生します。
  • 脅威: サーバーのログに残らないことが多く、WAF (Web Application Firewall) などによる検知が困難な場合があります。

XSSによる被害とコンテキスト内スクリプト実行の手口

XSSが成功すると、攻撃者のスクリプトはユーザーのブラウザ上で、そのWebサイトと同じオリジン(権限)で実行されます。これにより、以下のような深刻な被害が生じます。

  1. セッションハイジャック: 認証CookieにHttpOnlyが設定されていなければ、document.cookieからセッションIDを盗まれる可能性があります。HttpOnlyがあっても、XSSによる認証済みリクエストの実行まで防げるわけではありません。
  2. 不正な操作の実行: ユーザーの権限で、アプリケーション内の任意の操作(パスワード変更、送金、メッセージ送信など)をバックグラウンドで実行させます。
  3. フィッシング: 偽のログインフォームをDOM上に描画し、ユーザーの認証情報を直接盗み出します。
  4. マルウェアの配布: ユーザーのブラウザをエクスプロイトキットにリダイレクトさせ、PCをマルウェアに感染させます。

XSSに対するモダンな防御策

XSSを防ぐためには、多層防御 (Defense in Depth) のアプローチが不可欠です。

1. コンテキストに応じたエスケープ処理 (Output Encoding)

最も基本的かつ重要な対策は、ユーザーからの入力をWebページに出力する際に、無害な文字列に変換するエスケープ(エンコーディング)処理です。重要なのは、データが出力されるコンテキスト(HTML本文、HTML属性、JavaScript内、CSS内、URL内など) に応じて、適切なエスケープ方式を選択することです。現代の多くのWebフレームワーク(React, Vue, Angularなど)は、デフォルトでHTMLエスケープを行いますが、それでも注意が必要です。

2. CSP (Content Security Policy) の導入

CSPは、XSSに対する非常に強力な防御メカニズムであり、ブラウザが読み込み・実行を許可するリソースのホワイトリストをHTTPヘッダーで定義します。

1
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;

これにより、仮に攻撃者がインラインスクリプト <script>alert(1)</script> を注入できたとしても、CSPによって実行がブロックされます。

3. HttpOnly Cookie属性の活用

セッションIDなどを格納するCookieに HttpOnly 属性を付与することで、JavaScript (例: document.cookie) からそのCookieにアクセスできなくなります。これはXSS自体の発生を防ぐものではありませんが、XSSによるセッションハイジャックのリスクを大幅に低減させる重要な緩和策です。


2. CSRF (Cross-Site Request Forgery) の本質

CSRFは、攻撃者がユーザーを罠サイトに誘導し、そのユーザーがすでに認証(ログイン)している別のWebサイトに対して、意図しないリクエストを強制的に送信させる攻撃です。

XSSが「ブラウザ内で不正なスクリプトを実行させる」のに対し、CSRFは「ブラウザの標準的な動作(Cookieの自動送信)を悪用して不正なリクエストを送信させる」という点が決定的に異なります。

CSRFのメカニズム:「Cookieの自動送信」の悪用

ブラウザは、Cookieの送信条件が満たされるリクエストに、セッションCookieなどを自動的に付与します。別サイトの画像タグやフォームからのリクエストでも送信される場合がありますが、SameSite、Secure、Domain、Path、ブラウザのCookie制限などによって送信されない場合もあります。

攻撃のシナリオ:

以下は、認証CookieがSameSite=None; Secureでクロスサイト送信を許可し、CSRFトークンやOrigin検証を実施していない脆弱なアプリを想定した説明用の例です。bank.exampleとattacker.invalidは別サイトの予約ドメインです。

  1. ユーザーが銀行サイト (bank.example) にログインし、セッションCookieを受け取ります。
  2. ユーザーが別のタブで攻撃者の罠サイト (attacker.invalid) を閲覧します。
  3. 罠サイトには、以下のような隠しフォームと自動送信スクリプトが仕掛けられています。
    1
    2
    3
    4
    5
    
    <form action="https://bank.example/transfer" method="POST" id="csrf-form">
        <input type="hidden" name="toAccount" value="ATTACKER_ACCOUNT">
        <input type="hidden" name="amount" value="1000000">
    </form>
    <script>document.getElementById('csrf-form').submit();</script>
    
  4. ブラウザは bank.example へのPOSTリクエストを送信します。この時、銀行サイトのセッションCookieが自動的に付与されます。
  5. 銀行サーバーは、正規のセッションCookieが含まれているため、正当なユーザーからのリクエストとして処理し、不正な送金が実行されます。

CSRFに対する防御策の歴史的変遷と最新プラクティス

CSRFを防ぐためには、リクエストが「意図した正当なページから送信されたものか」を検証する必要があります。

1. CSRFトークン (Anti-CSRF Tokens) : 伝統的かつ確実な防御

最も古くから広く使われている確実な防御策は、CSRFトークン(Synchronizer Token Pattern)です。

  • サーバーは、セッションごとに予測不可能なランダムなトークンを生成し、サーバー側(セッションなど)に保存します。
  • クライアントに送信するHTMLフォーム内に、このトークンをhiddenフィールドとして埋め込みます。
  • フォーム送信時、サーバーは送られてきたトークンとサーバー側に保存されているトークンを比較し、一致した場合のみリクエストを処理します。 攻撃者は罠サイトからリクエストを送信させることはできても、対象サイトのページを読み取って正しいトークンを取得することはできない(Same-Origin Policyにより)ため、攻撃は失敗します。

サーバー側で状態(セッション)を持たないAPIなどでよく用いられる手法です。

  • サーバーはランダムなトークンを生成し、Cookieとしてクライアントに送信します。
  • クライアントのJavaScriptは、そのCookieの値を読み取り、リクエストヘッダー(例: X-CSRF-Token)にセットして送信します。
  • サーバーは、Cookie内のトークン値とヘッダー内のトークン値が一致するかを検証します。 上記は単純な一致検査の説明です。Cookieを注入できる攻撃者を考慮すると、それだけに依存する方式は推奨できません。実装ではフレームワークの機能を優先し、Double Submitを採用する場合は、サーバーの秘密鍵でHMAC署名し、現在の認証セッションに結び付けたトークンを検証するSigned Double Submit方式を使用します。カスタムヘッダーを使うAPIでは、信頼しないオリジンへの認証付きCORS許可も避けます。

3. SameSite Cookie属性 : モダンブラウザによる強力な防御

CookieのSameSite属性は、クロスサイトリクエスト時のCookie送信を制御する防御層です。一般的なアプリでは、CSRFトークンやOrigin検証を置き換えるものと考えず、組み合わせて利用します。

  • SameSite=Strict: リンククリックなどのトップレベルナビゲーションを含め、いかなるクロスサイトリクエストでもCookieは送信されません。最も安全ですが、別サイトからのリンクでログイン状態が維持されないなど、UXに影響を与える可能性があります。
  • SameSite=Lax: 画像の読み込みやPOSTリクエストなどのクロスサイトリクエストではCookieは送信されませんが、リンククリック(GETリクエスト)によるトップレベルナビゲーションでは送信されます。現在の多くのブラウザのデフォルトの挙動です。これにより、悪意のあるPOSTフォーム送信によるCSRFの大部分を防ぐことができます。
  • SameSite=None: SameSiteによるクロスサイト送信制限を設けません。Secureも必要です。他のCookie送信条件やブラウザの制限まで無効にするわけではありません。

SameSiteはサイト単位の仕組みで、オリジン単位ではありません。同じ登録可能ドメインのサブドメインは同一サイトと扱われる場合があります。また、GETで状態を変更する設計はLaxでも保護できません。状態変更は適切なHTTPメソッドで行い、サーバー側のCSRF検証を組み合わせてください。ブラウザ既定のLaxには例外もあるため、必要な属性は明示的に設定します。


XSSとCSRFの相関関係とまとめ

以下の図は、攻撃の流れの違いを示しています。

  graph TD
    subgraph XSS Attack
        A["攻撃者"] -- "悪意のあるスクリプト注入" --> B["Webサーバー"]
        B -- "スクリプトを含むページ" --> C["被害者のブラウザ"]
        C -- "セッションIDの盗難 (HttpOnlyなし)" --> A
    end

    subgraph CSRF Attack
        D["被害者"] -- "ログイン" --> E["Webサーバー"]
        D -- "閲覧" --> F["攻撃者の罠サイト"]
        F -- "強制リクエスト (Cookie付き)" --> E
    end

XSSとCSRFは異なる脆弱性ですが、XSSが存在する場合、ほとんどのCSRF対策は無効化されます。なぜなら、XSSによって実行されたスクリプトは、正規のページ内で動いているため、CSRFトークンを読み取ったり、同じオリジンからリクエストを送信したりすることが可能だからです。

したがって、Webアプリケーションのセキュリティを担保するためには、まずXSSを徹底的に封じ込め(適切なエスケープとCSP)、その上でCSRF対策(SameSite CookieとCSRFトークン)を実装するという、強固な基盤構築が求められます。

開発者は、フレームワークが提供するセキュリティ機能を過信せず、これらの脆弱性の本質的なメカニズムを理解し、適切な層での防御を設計することが重要です。

参考資料

comments powered by Disqus