CSRF を深く理解する
クロスサイトリクエストフォージェリ(CSRF)攻撃を詳しく探り、その仕組みを説明し、例を示し、ウェブアプリケーションのセキュリティを強化するためのさまざまな防止方法を詳述します。
ウェブ開発、特にクッキーを扱っていると、「この設定は CSRF を防ぐのに役立ちます」といったフレーズをよく耳にします。しかし、多くの人は「CSRF」の意味を漠然としか理解していないのが現状です。
今日は、一般的なウェブセキュリティの弱点である CSRF(クロスサイトリクエストフォージェリ)について深く掘り下げます。これにより、CSRF 関連の問題をより効果的に扱えるようになります。
CSRF とは何か?
CSRF(クロスサイトリクエストフォージェリ)は、攻撃者が認証されたユーザーを騙して意図しない操作を実行させるウェブ攻撃の一種です。簡単に言うと、「ハッカーがユーザーのフリをして不正行為を行う」ということです。
CSRF が機能する仕組み
CSRF を理解するためには、いくつかの重要な概念を把握する必要があります:
ブラウザの同一オリジンポリシー
同一オリジンポリシーは、ブラウザ内でのセキュリティ機能であり、あるオリジンからのドキュメントやスクリプトが他のオリジンからのリソースとどのようにやり取りできるかを制限します。
オリジンはプロトコル(HTTP または HTTPS など)、ドメイン名、ポート番号で構成されます。例えば、https://example.com:443
は一つのオリジンであり、https://demo.com:80
は別のオリジンです。
同一オリジンポリシーは異なるオリジンからのページ間のデータアクセスを制限し、それはつまり:
- あるオリジンの JavaScript は他のオリジンの DOM を読み取ることができません
- あるオリジンの JavaScript は他のオリジンの Cookie、IndexedDB、localStorage を読み取ることができません
- あるオリジンの JavaScript は他のオリジンに AJAX リクエストを送信できません(CORS を使用する場合を除く)
しかし、ウェブの開放性と相互運用性を維持するために(CDN からのリソースを読み込む、またはログ記録用にサードパーティ API にリクエストを送信するなど)、同一オリジンポリシーはクロスオリジンのネットワークリクエストを制限していません:
- ページは任意のオリジンに GET や POST リクエストを送信できます(画像を読み込んだりフォームを送信したりする場合など)
- 任意のオリジンのリソースは含められます(
<script>
,<img>
,<link>
,<iframe>
タグなど)
自動クッキー送信メカニズム
自動クッキー送信メカニズムはブラウザの重要な機能です。ブラウザがドメインにリクエストを送信すると、そのドメインのすべてのクッキーを自動的に付加します。このプロセスは自動で、JavaScript のコードやユーザーの操作を必要としません。
このメカニズムにより、ウェブサイトはユーザーのログイン状態 を簡単に記憶できるようになります。なぜなら、各リクエストにユーザーの識別情報が自動的に付加されるからです。
例えば、銀行のウェブサイト(bank.com
)にログインし、認証クッキーを得た場合、ステートメントを表示するためにクリックすると、ブラウザは自動的に bank.com
に一致するすべてのクッキーを見つけ、ステートメントリクエストに付加します。銀行のサーバーは、バックエンドからユーザーを識別し、ステートメント情報を返すことができます。
CSRF 攻撃のステップ
-
ユーザーが対象のウェブサイト(銀行サイトなど)にログインし、認証クッキーを取得します。 このステップでは自動クッキー送信メカニズムを使用します。銀行サイトが認証クッキーを設定すると、ブラウザはそのサイトに送信するすべてのリクエストにそのクッキーを自動的に付加します。
-
ログアウトせずに、ユーザーが悪意のあるウェブサイトを訪問します。 この時点では、同一オリジンポリシーにより、悪意のあるサイトは銀行サイトのクッキーを直接読み取ったり、変更したりできません。これはユーザーの識別情報が直接盗まれないように保護しています。
-
悪意のあるサイトは対象のサイト(送金操作など)にリクエストを含めます。 同一オリジンポリシーはクロスオリジンのアクセスを制限しますが、
<img>
や<form>
タグを通じてのクロスオリジンネットワークリクエストを許可します。攻撃者はこの「抜け穴」を利用します。 -
ユーザーのブラウザがこのリクエストを自動的に送信し、対象のサイトのクッキーも同時に送信されます。 これが CSRF 攻撃の核心です。同一オリジンポリシーがクロスオリジンリクエストを許可することと、自動クッキー送信メカニズムを利用しています(悪意のあるサイトがトリガーしたリクエストでも、ドメインに一致するクッキーが付加されます)。
-
対象サイトがリクエストを受け取り、クッキーが有効であることを確認し、操作を実行します。 サーバーは、このリクエストが正当なユーザー操作から来たのかを判断できません。なぜなら、付加されたクッキーは有効だからです。
CSRF 攻撃の例
CSRF 攻撃がどのように行われるかを具体例で説明しましょう。ここでは、架空の銀行ウェブサイト bank.com
を例に使います。
まず、ユーザーが https://bank.com
を訪れ、アカウントにログインします。
ログインに成功すると、サーバーが認証クッキーを設定します。例えば:
ユーザーは銀行ウェブサイトで送金操作を行い、例えば Alice に 1000 ドルを送金します。この操作は次のようなリクエストを送信するかもしれません:
ここで、攻撃者が https://evil.com
という悪意のあるウェブサイトを作成し、次のような HTML を含んでいます:
ユーザーが銀行アカウントからログアウトしないまま https://evil.com
リンクをクリックすると、bank.com
にすでにログイン済みで有効な session_id
クッキーを持っているため、
悪意のあるページがロードされた後、隠されたフォームが自動的に送信され、送金リクエストが https://bank.com/transfer
に送信されます。
ユーザーのブラウザはこのリクエストに自動的に bank.com
のクッキーを付加します。bank.com
サーバーはこのリクエストを受け取り、クッキーが有効であることを確認した後、この不正な送金操作を実行します。
CSRF 攻撃を防ぐ一般的な方法
ここでは、一般的に用いられる CSRF 防御方法をいくつか紹介します。それぞれの方法の原理と、CSRF 攻撃をどのように効果的に防止するかを詳しく説明します:
CSRF トークンの使用
CSRF トークンは、CSRF 攻撃に対する最も一般的かつ効果的な方法の一つです。方法は以下の通りです:
- サーバーは各セッションに対してユニークで予測不可能なトークンを生成します。
- このトークンはすべてのフォームに埋め込まれ、機密操作用です。
- ユーザーがフォームを送信すると、サーバーがトークンの有効性を確認します。
CSRF トークンはユーザーのセッションに結び付けられたユニークな値であり、攻撃者はこの値を知ることも予測することもできないため(各セッションで異なるので)、攻撃者がユーザーを騙してリクエストを送信さ せても、サーバーは有効な CSRF トークンがないためにリクエストを拒否します。
実装例:
サーバー側(Node.js および Express を使用):
フロントエンドの JavaScript:
Referer
ヘッダーの確認
Referer
ヘッダーには、リクエストを発行したページの URL が含まれています。Referer
ヘッダーを確認することで、サーバーはリクエストが正当なソースから来たかどうかを判断できます。
CSRF 攻撃は通常、異なるドメインから来るため、Referer
ヘッダーには攻撃者のドメイン名が表示されます。Referer
が期待される値かどうかを確認することで、不明なソースからのリクエストをブロックできます。
ただし、この方法は必ずしも信頼性が高いとは言えません。一部のブラウザは Referer
ヘッダーを送信しない場合があり、ユーザーはブラウザー設定やプラグインを通じて Referer
ヘッダーを無効にできるからです。
実装例:
SameSite
クッキー属性の使用
SameSite
は、クッキーがクロスサイトリクエストで送信されるかどうかを制御するためのクッキー属性です。3 つの可能な値があります:
Strict
: クッキーは同一サイトリクエストでのみ送信されます。Lax
: クッキーは同一サイトのリクエストおよびトップレベルのナビゲーションで送信されます。None
: クッキーはすべてのクロスサイトリクエストで送信されます(Secure
属性と共に使用する必要があります)。
SameSite
が Strict
に設定されている場合、サードパーティのウェブサイトからのクッキー送信を完全に防ぐことができ、CSRF 攻撃を効果的に防止します。
SameSite
が Lax
に設定されている場合、外部リンクからウェブサイトに入るような一般的なクロスサイト利用ケースを許可しながら、機密操作を保護します。
実装例:
カスタムリクエストヘッダーの使用
AJAX リクエストにはカスタムリクエストヘッダーを追加することができます。同一オリジンポリシーの制限により、攻撃者はクロスオリジンリクエストでカスタムヘッダーを設定することができません。サーバーはこのカスタムヘッダーの存在を確認して、リクエストの正当性を検証することができます。
実装例:
フロントエンドでは:
サーバー側では:
ダブルクッキーバリデーション
ダブルクッキーバリデーションは、効果的な CSRF 防御手法です。その核心原理は、サーバーがランダムなトークンを生成し、クッキーとして設定し、ページに埋め込みます(通常は隠しフォームフィールドとして)。ブラウザがリクエストを送信すると、クッキーが自動的に含まれ、ページの JavaScript がトークンをリクエストパラメータとして送信します。サーバーは、クッキーのトークンとリクエストパラメーターのトークンが一致するかどうかを確認します。
攻撃者はクロスサイトリクエストで対象ウェブサイトのクッキーを含めることはできますが、クッキーの値を読み取ったり変更したりすることはできませんし、ページのトークン値にアクセスしたり変更したりすることもできません。クッキーとパラメータの両方から取得したトークンを含むリクエストが必要にすることで、クッキーを読み取る権限があるソースからのリクエストであることを保証し、CSRF 攻撃を効果的に防御します。
機密操作の再認証の使用
特に機密性の高い操作(パスワードの変更や大口送金など)では、ユーザーに再認証を要求できます。 これにより、ユーザーには追加のセキュリティチェックポイントが提供されます。たとえユーザーが CSRF 攻撃を成功させたとしても、再認証ステップを通過できません。
実装の提案:
- 機密操作を実行する前に、別の認証ページにリダイレクトします。
- このページで、ユーザーにパスワードやその他の本人確認情報を入力するよう要求します。
- 認証が通過したら、一回限りのトークンを生成し、このトークンを以降の機密操作で使用します。
まとめ
この詳細な議論を通じて、皆さんが CSRF 攻撃についてより包括的な理解を持つことができたことを願っています。 CSRF がどのように機能するかを学ぶだけでなく、さまざまな効果的な防御策についても探求しました。すべてのこれらの方法は、ウェブアプリケーションのセキュリティを効果的に向上させることができます。
この知識が、日々の開発で CSRF 関連の問題をよりよく扱い、より安全なウェブアプリケーションを構築するのに役立つことを願っています。