Webアプリケーションにおいて、CSRF(Cross-Site Request Forgery:クロスサイトリクエストフォージェリ)は、ユーザーが意図しない操作を強制的に実行させる危険な攻撃手法です。OWASPでも重要な脆弱性として位置づけられており、すべてのWeb開発者が理解しておくべきセキュリティ脅威です。
この記事では、CSRFとは何か、攻撃が成立する条件、CSRFトークンによる対策、SameSite Cookie属性の活用について、実践的な観点から解説します。
CSRFとは何か
CSRF(Cross-Site Request Forgery:クロスサイトリクエストフォージェリ)は、ユーザーが認証済みのWebアプリケーションに対して、悪意のあるリクエストを強制的に送信させる攻撃手法です。「シーサーフ」や「クロスサイトリクエストフォージェリ」とも呼ばれます。
CSRF攻撃の基本的な流れ
sequenceDiagram
participant User as ユーザー
participant Bank as 銀行サイト
participant Attacker as 攻撃者サイト
Note over User,Bank: Step 1: ユーザーが正規サイトにログイン
User->>Bank: ログイン
Bank-->>User: セッションCookie発行
Note over User,Attacker: Step 2: ユーザーが悪意のあるサイトを訪問(ログイン状態のまま)
User->>Attacker: アクセス
Attacker-->>User: 不正なフォーム/imgタグを含むHTML
Note over User,Bank: Step 3: ブラウザが自動的に銀行サイトへリクエストを送信
User->>Bank: 送金リクエスト + Cookie
Note over Bank: 正規のリクエストとして処理
Bank-->>User: 送金完了CSRF攻撃で何ができるのか
CSRF攻撃が成功すると、攻撃者は被害者の権限を悪用して以下のような操作を実行できます。
- 不正送金: 銀行口座から攻撃者の口座への送金
- パスワード変更: ユーザーアカウントのパスワードを変更してアカウントを乗っ取る
- メールアドレス変更: 通知先を攻撃者のメールアドレスに変更
- 設定変更: プライバシー設定やセキュリティ設定の無効化
- 商品購入: ECサイトでの不正な購入処理
CSRFとXSSの違い
CSRFとXSS(クロスサイトスクリプティング)は混同されやすいですが、攻撃の仕組みが異なります。
| 項目 | CSRF | XSS |
|---|---|---|
| 攻撃の対象 | サーバー側の処理 | クライアント側(ブラウザ) |
| 攻撃の方法 | ユーザーの認証情報を悪用したリクエスト送信 | 悪意のあるスクリプトの注入・実行 |
| 必要な条件 | ユーザーがログイン状態であること | 脆弱なサイトへのスクリプト注入が可能であること |
| 攻撃者が得るもの | ユーザーの権限での操作実行 | Cookie、セッション情報、入力データなど |
重要な点として、XSS脆弱性が存在する場合、CSRFの対策(CSRFトークンなど)を回避できる可能性があります。そのため、CSRF対策だけでなく、XSS対策も併せて実施することが重要です。
CSRF攻撃が成立する条件
CSRF攻撃が成功するためには、以下の3つの条件がすべて満たされる必要があります。
条件1: 状態を変更するHTTPリクエストが存在する
Webアプリケーションに、サーバー側の状態を変更する機能(送金、パスワード変更、設定更新など)が存在することが前提条件です。
状態変更リクエストの例:
|
|
条件2: Cookieのみで認証が行われている
サーバーがリクエストの正当性をCookie(セッションCookie)のみで判断している場合、攻撃が可能になります。ブラウザは、クロスサイトリクエストであっても自動的にCookieを付与して送信するためです。
条件3: リクエストパラメータが予測可能である
攻撃者がリクエストに必要なパラメータをすべて推測できる場合、有効な攻撃リクエストを構築できます。逆に、予測不可能なトークンが含まれていれば、攻撃は失敗します。
flowchart TD
subgraph Conditions["CSRF攻撃成立の3条件"]
A["1. 状態変更リクエストが存在する<br>→ 送金、設定変更、購入など"]
B["2. Cookieベースの認証のみ<br>→ ブラウザが自動的にCookieを送信"]
C["3. パラメータが予測可能<br>→ 攻撃者が有効なリクエストを作成可能"]
end
A --> D{"3条件すべてが揃う"}
B --> D
C --> D
D --> E["CSRF攻撃が可能"]CSRF攻撃の具体例
GETリクエストを悪用した攻撃
最もシンプルなCSRF攻撃は、GETリクエストで状態変更を行う脆弱なアプリケーションを狙うものです。
脆弱な実装例(絶対に避けるべき設計):
|
|
攻撃コード:
|
|
被害者がこのページを訪問すると、ブラウザは自動的に画像を読み込もうとし、送金リクエストが実行されます。
POSTリクエストを悪用した攻撃
POSTリクエストを使用していても、CSRF攻撃は可能です。攻撃者は隠しフォームを使用します。
攻撃コード:
|
|
このHTMLページを被害者が開くと、JavaScriptによってフォームが自動送信されます。
JSONベースのAPIに対する攻撃
モダンなAPIでもCSRF攻撃のリスクは存在します。ただし、application/jsonのContent-Typeを使用する場合、CORSのプリフライトリクエストが発生するため、サーバー側で適切にCORS設定がされていれば防御できます。
|
|
同一オリジンポリシーとCORSにより、このリクエストはプリフライトが必要となり、サーバーがCORSを許可していなければブロックされます。
効かないCSRF対策
CSRF対策として誤解されやすい、実際には効果のない対策を確認しておきましょう。
Cookieをシークレットにする
Cookieの値を秘密にしても、ブラウザが自動的にCookieを送信する以上、CSRF対策にはなりません。
POSTメソッドのみを使用する
POSTリクエストもフォームの自動送信で簡単に偽装できるため、CSRF対策としては不十分です。
HTTPSを使用する
HTTPSは通信の暗号化を提供しますが、CSRFを防ぐことはできません。ただし、CSRF対策が信頼できるものであるための前提条件としてHTTPSは必須です。
Refererヘッダーの検証
Refererヘッダーは偽装される可能性があり、またプライバシー設定やブラウザの設定によって送信されない場合もあるため、単独での対策としては不十分です。
マルチステップトランザクション
複数のステップを経る処理であっても、攻撃者がすべてのステップを予測できれば攻撃は可能です。
CSRFトークンによる対策
CSRFトークンは、CSRF攻撃を防ぐための最も効果的で広く採用されている対策方法です。
CSRFトークンの仕組み
CSRFトークンは、サーバーが生成する予測不可能なランダムな値で、フォームやリクエストに含めることで、リクエストが正規のものであることを検証します。
sequenceDiagram
participant User as ユーザー
participant Server as サーバー
participant Attacker as 攻撃者サイト経由
Note over User,Server: Step 1: サーバーがCSRFトークンを生成・送信
User->>Server: フォームページをリクエスト
Server-->>User: HTML + CSRFトークン(hidden input)
Note over User,Server: Step 2: ユーザーがフォームを送信(トークン付き)
User->>Server: POST + CSRFトークン
Server->>Server: トークン検証(一致すれば許可)
Server-->>User: 処理成功
Note over Attacker,Server: Step 3: 攻撃者のサイトからのリクエスト(トークンなし)
Attacker->>Server: POST(トークンなし or 不正)
Server->>Server: トークン検証(不一致で拒否)
Server-->>Attacker: 403 ForbiddenSynchronizer Token Pattern(同期トークンパターン)
最も一般的なCSRFトークンの実装パターンです。サーバーがセッションごとにトークンを生成し、フォームに埋め込みます。
サーバー側の実装例(Node.js/Express):
|
|
HTMLフォームへの埋め込み:
|
|
Double Submit Cookie(二重送信Cookie)
ステートレスな実装が必要な場合に使用されるパターンです。CSRFトークンをCookieとリクエストボディ(またはヘッダー)の両方に含め、サーバー側で両者が一致することを確認します。
実装例(Node.js/Express):
|
|
クライアント側のJavaScript:
|
|
署名付きDouble Submit Cookie(推奨)
基本的なDouble Submit Cookieパターンは、サブドメインからのCookie注入攻撃に対して脆弱な可能性があります。より安全な実装として、HMACを使用した署名付きトークンが推奨されます。
|
|
CSRFトークンの要件
効果的なCSRFトークンは以下の要件を満たす必要があります。
| 要件 | 説明 |
|---|---|
| 一意性 | ユーザーセッションごとに一意であること |
| 秘密性 | 攻撃者が推測できないこと |
| 予測不可能性 | 暗号論的に安全な乱数で生成されること |
| セッション紐付け | 特定のユーザーセッションに紐づいていること |
SameSite Cookie属性によるCSRF対策
SameSite Cookie属性は、ブラウザがクロスサイトリクエストでCookieを送信するかどうかを制御する機能です。CSRF対策として非常に効果的であり、現在のモダンブラウザではデフォルトでLaxが適用されます。
SameSite属性の3つの値
| 属性値 | 動作 |
|---|---|
| Strict | クロスサイトリクエストでは一切送信しない。最も安全だがUXに影響する可能性あり |
| Lax(デフォルト) | トップレベルナビゲーション + GETのみ送信。セキュリティとUXのバランスが良い |
| None | すべてのリクエストで送信(Secure必須)。クロスサイト機能が必要な場合のみ使用 |
SameSite=Strict
最も厳格な設定で、クロスサイトリクエストではCookieが一切送信されません。
|
|
注意点: 外部サイトからのリンクでもCookieが送信されないため、ユーザーは毎回ログインが必要になる可能性があります。銀行サイトなど、外部リンクからのアクセスが想定されない場合に適しています。
SameSite=Lax
トップレベルナビゲーション(リンククリックなど)かつGETメソッドの場合のみCookieを送信します。POSTフォームやAJAXリクエストではCookieが送信されません。
|
|
Laxは多くのWebアプリケーションに適したバランスの良い設定です。2020年以降、主要ブラウザはデフォルトでLaxを適用しています。
SameSite=None
クロスサイトリクエストでもCookieを送信します。サードパーティCookieが必要な場合(埋め込みウィジェット、OAuth連携など)に使用しますが、必ずSecure属性と併用する必要があります。
|
|
SameSite属性の実装例
Node.js/Express:
|
|
Python/Django:
|
|
PHP:
|
|
SameSite属性の注意点
SameSite Cookie属性は強力なCSRF対策ですが、単独での防御に頼るべきではありません。
- レガシーブラウザの非対応: 古いブラウザではSameSite属性がサポートされていない場合があります
- サブドメインの問題: サブドメインからの攻撃には対応できない場合があります
- GETリクエストの問題: Laxモードでは、GETリクエストによる状態変更(避けるべき設計)に対しては防御できません
そのため、SameSite属性は多層防御の一部として、CSRFトークンと併用することが推奨されます。
Fetch Metadataヘッダーによる対策
Fetch Metadataは、リクエストの送信元に関する追加情報をサーバーに提供するHTTPヘッダーです。サーバー側でこれらのヘッダーを検証することで、クロスサイトリクエストをブロックできます。
主要なFetch Metadataヘッダー
| ヘッダー | 説明 |
|---|---|
Sec-Fetch-Site |
リクエストの送信元とターゲットの関係(same-origin, same-site, cross-site, none) |
Sec-Fetch-Mode |
リクエストのモード(navigate, cors, no-corsなど) |
Sec-Fetch-Dest |
リクエストの目的(document, script, imageなど) |
Sec-Fetch-User |
ユーザーの操作によるリクエストかどうか |
サーバー側での検証実装
|
|
ブラウザサポート状況
Fetch Metadataヘッダーは、2023年3月以降のすべての主要ブラウザでサポートされており、グローバルカバレッジは98%を超えています。レガシーブラウザのためのフォールバック(Origin/Refererヘッダー検証やCSRFトークン)を併用することが推奨されます。
フレームワーク別のCSRF対策
多くのWebフレームワークには、CSRF対策機能が組み込まれています。これらを活用することで、安全かつ効率的に対策を実装できます。
Express.js(csurf)
注意: csurfパッケージは現在メンテナンスされていないため、代替としてcsrf-csrfなどの使用が推奨されます。
|
|
Django
Djangoには堅牢なCSRF保護機能が組み込まれています。
|
|
|
|
Ruby on Rails
Rails 5.2以降では、デフォルトでCSRF保護が有効になっています。
|
|
<!-- ERBテンプレート -->
<%= form_with url: transfer_path do |form| %>
<%= form.text_field :to_account %>
<%= form.number_field :amount %>
<%= form.submit '送金' %>
<% end %>
Spring Security(Java)
|
|
Angular
Angularには、Cookie-to-Headerパターンが組み込まれています。
|
|
CSRF対策のベストプラクティス
効果的なCSRF対策を実装するためのベストプラクティスをまとめます。
多層防御の実装
単一の対策に頼らず、複数の対策を組み合わせることで、より強固な防御を実現できます。
flowchart TB
A["第1層: SameSite Cookie<br>ブラウザレベルでクロスサイトリクエストを制限"]
B["第2層: CSRFトークン検証<br>予測不可能なトークンでリクエストの正当性を確認"]
C["第3層: Fetch Metadata検証<br>リクエストの送信元を検証"]
D["第4層: Origin/Referer検証<br>補助的な送信元チェック(フォールバック)"]
A --> B --> C --> Dチェックリスト
| 項目 | 推奨事項 |
|---|---|
| CSRFトークン | すべての状態変更リクエストに必須 |
| SameSite Cookie | セッションCookieにLax以上を設定 |
| Secure属性 | 本番環境では必ず有効化 |
| HttpOnly属性 | セッションCookieに設定 |
| GETリクエスト | 状態変更には絶対に使用しない |
| フレームワーク | 組み込みのCSRF対策機能を活用 |
| XSS対策 | CSRF対策と併せて実施(XSSはCSRF対策を無効化できる) |
ログインフォームのCSRF対策
ログインフォームもCSRF攻撃の対象となりえます(ログインCSRF)。攻撃者のアカウントにユーザーをログインさせ、その後の活動を監視する攻撃が可能です。
ログインCSRF対策として、プレセッション(認証前のセッション)でCSRFトークンを発行し、ログインフォームに含めることが推奨されます。
まとめ
CSRF(クロスサイトリクエストフォージェリ)は、ユーザーの認証情報を悪用してサーバーに不正なリクエストを送信する攻撃です。この記事で解説した内容を振り返ります。
- CSRFの仕組み: ユーザーがログイン状態で悪意のあるサイトを訪問すると、ブラウザがCookieを自動送信することを悪用した攻撃
- 成立条件: 状態変更リクエストの存在、Cookieベースの認証、予測可能なパラメータの3条件が揃った場合に攻撃が成立
- CSRFトークン: 予測不可能なトークンをリクエストに含め、サーバーで検証することで対策
- SameSite Cookie: ブラウザレベルでクロスサイトリクエストへのCookie送信を制限
- 多層防御: 単一の対策ではなく、複数の対策を組み合わせることが重要
CSRFトークンとSameSite Cookie属性を組み合わせた多層防御を実装し、安全なWebアプリケーションを構築しましょう。