CSRF

ユーザーが認証済みの Web サイトに対して、攻撃者が意図しないリクエストを送信させる攻撃手法

セキュリティWeb
CSRF」の技術書を見る →

CSRF とは

CSRF (Cross-Site Request Forgery、クロスサイトリクエストフォージェリ) は、ユーザーが認証済みの Web サイトに対して、攻撃者が用意した罠ページから意図しないリクエストを送信させる攻撃手法である。ブラウザが Cookie を自動送信する仕組みを悪用する。

OWASP Top 10 に長年ランクインしていた攻撃で、2013 年版では 8 位だった。SameSite Cookie の普及により脅威は低下したが、レガシーシステムや Cookie ベースの認証を使うアプリケーションでは依然として注意が必要だ。

攻撃の仕組み

攻撃は次の 4 ステップで成立する。

1. ユーザーが銀行サイト (bank.example.com) にログイン
   → セッション Cookie がブラウザに保存される

2. ユーザーが攻撃者の罠ページ (evil.example.com) を閲覧

3. 罠ページに埋め込まれた HTML が銀行サイトへ POST リクエストを自動送信
   <form action="https://bank.example.com/transfer" method="POST">
     <input type="hidden" name="to" value="attacker-account" />
     <input type="hidden" name="amount" value="1000000" />
   </form>
   <script>document.forms[0].submit();</script>

4. ブラウザがセッション Cookie を自動付与
   → 銀行サイトは正規ユーザーからのリクエストと判断
   → 送金が実行される

攻撃者はユーザーの Cookie を盗む必要がない。ブラウザが自動的に Cookie を付与してくれるため、ユーザーが罠ページを開くだけで攻撃が成立する。

対策

CSRF トークン (Synchronizer Token Pattern)

フォームに一意なトークンを埋め込み、サーバーで検証する。最も伝統的で確実な対策だ。

<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="a1b2c3d4..." />
  <!-- フォームの内容 -->
</form>

攻撃者は CSRF トークンの値を知ることができないため、正しいトークンを含むリクエストを偽造できない。

SameSite 属性を設定すると、クロスサイトリクエストに Cookie を送信しなくなる。

動作CSRF 防御
StrictCookie を設定したサイトと同一サイト起点のリクエストにしか送信しないほぼ防御 (同一サイト内の別オリジンを踏み台にされる経路は残る)
Lax同一サイト起点に加えて、クロスサイトでは「トップレベルナビゲーション」かつ「安全なメソッド (GET など)」の両方を満たすリクエストにのみ送信する。画像・iframe・fetch() のクロスサイト GET や、クロスサイトの POST では送信しない条件付きで防御
None常に Cookie を送信 (Secure 必須)防御なし

主要ブラウザは SameSite の指定がない Cookie を Lax として扱う。Chrome では 2020 年 2 月の 80 で入る予定だったが、Stable の初回ロールアウトには含まれず、その後の段階展開で有効化された。

ただし、この既定値を CSRF 対策として当てにするのは危険だ。既定として適用される Lax には緩和措置があり、発行から 2 分以内の Cookie はクロスサイトのトップレベル POST でも送信される。POST を使うログインフローが壊れるのを避けるための暫定的な介入で、Chrome は将来の削除を予告している。つまりログイン直後の 2 分間だけ穴が開き、SameSite=Lax を明示した Cookie とは挙動が違う。加えて、GET リクエストで副作用を起こす API (GET /delete-account) は Lax でも防御できない。属性は既定に頼らず明示的に指定し、トークン検証と併用する。

Origin / Referer ヘッダー検証

リクエストの Origin ヘッダーが自サイトのドメインと一致するか検証する。CSRF トークンの管理が不要で実装が簡単だ。現代のブラウザはクロスオリジンのリクエストと、同一オリジンでも GET / HEAD 以外のメソッドのリクエストに Origin を付与する。ただしリダイレクトを経由した場合やサンドボックス化された文書からの送信では値が null になり、間にあるプロキシがヘッダーを落とすこともある。ヘッダーが無いリクエストを通す実装にすると検証そのものが無意味になるため、欠落は拒否側に倒す。Referer はパスまで含むため送信抑止を受けやすく、Origin を主にして Referer は補助に留めるのが実務的だ。

SPA + API 構成での CSRF

SPAJWTAuthorization ヘッダーで送信する構成では、ブラウザが自動送信しないため CSRF のリスクは低い。

認証方式CSRF リスク理由
セッション Cookie高いブラウザが自動送信
JWT in Cookie高いブラウザが自動送信
JWT in Authorization ヘッダー低いJavaScript で明示的に付与
JWT in localStorage低い (XSS リスクあり)ブラウザが自動送信しない

JWT を Cookie に保存する場合は、SameSite=Strict と CSRF トークンの併用が推奨される。

API Gateway + Lambda での対策

先に押さえておきたいのは、CORS 設定そのものは CSRF を止めないという点だ。Content-Typeapplication/x-www-form-urlencoded / multipart/form-data / text/plain のいずれかで、独自ヘッダーを持たないリクエスト (罠ページのフォーム送信と同じ形) はプリフライトが飛ばず、サーバーまで到達する。ブラウザが遮るのはレスポンスの読み取りだけなので、送金や削除といった副作用は起きてしまう。Fetch 仕様がこの形にサーバーの同意を要求しないのは、サーバー側が CSRF 対策済みである前提を置いているからである。

そのうえで、CORS 設定では Access-Control-Allow-Origin を自サイトのドメインに限定する。* (ワイルドカード) は使わない。Cookie を伴うリクエスト (AllowCredentials: true) に対しては、仕様上そもそも * を返せず、オリジンを明示する必要がある。

# SAM テンプレート
Cors:
  AllowOrigin: "'https://example.com'"
  AllowMethods: "'GET,POST,PUT,DELETE'"
  AllowHeaders: "'Content-Type,Authorization'"
  AllowCredentials: true

CORS はブラウザが強制する仕組みであり、curl やサーバーサイドからのリクエストは制限できない。CSRF を止めるのは Cookie 側の SameSite 指定とトークン検証・Origin 検証であって、CORS 設定は JavaScript からの読み取りを絞る別系統の防御である。両者を混同せず、認証・認可も含めて多層で実装する。

CSRF を扱う関連書籍も多い。

この記事は役に立ちましたか?

関連用語

関連する記事