blog.dopana

Back

CSRF(Cross-Site Request Forgery、クロスサイトリクエストフォージェリ)は、日本語では「関連サイト間のリクエスト偽造」と訳されます。専門用語のように聞こえますが、その本質は極めてシンプルです。それは、「ユーザーが意図していない不正な操作を、本人のブラウザを騙して勝手に実行させる攻撃」です。

1. 10歳でもわかる解説(ELI5)#

あなたが会員制VIPクラブ(オンライン銀行やSNS)に入店した場面を想像してみてください:

  1. 受付でログインを済ませると、手の甲に「VIP証明スタンプ」を押してもらいます(これがブラウザのセッションCookieです)。
  2. このスタンプがある限り、店員はいちいち身分証明書を確認せず、あなたの注文をすべて本人からのものとして受け付けます。
  3. クラブを出た後、あなたは怪しい露店(悪意あるWebサイト evil.com)に立ち寄りました。
  4. 露店の店主はこっそり紙に「このVIP会員の口座から10万円を店主に振り込む」と書き、あなたの手の甲のスタンプを勝手に紙に押し付けてVIPクラブへ送ってしまいました。
  5. VIPクラブの店員は本物のVIPスタンプを確認し \rightarrow 「本人が送った送金依頼だ」と信じ込んで \rightarrow 即座にお金を引き落としてしまいます!

あなたは送金ボタンなど一切押していませんが、ブラウザが「スタンプ(Cookie)」を勝手に差し出してしまったのです。これがCSRFの正体です。

flowchart LR
    subgraph AttackerSite["悪意あるサイト (evil.com)"]
        MaliciousScript["隠しスクリプト / 自動送信フォーム<br/>POST https://bank.com/transfer"]
    end

    subgraph UserBrowser["ユーザーのブラウザ (Client)"]
        Victim["ログイン中のユーザー<br/>(有効なCookieを保持)"]
    end

    subgraph BankServer["銀行サーバー (bank.com)"]
        Backend["Cookieを正当と判定<br/>-> 不正な送金を実行!"]
    end

    Victim -->|1. 悪意あるサイトを訪問| MaliciousScript
    MaliciousScript -->|2. クロスサイトリクエストを発火| Victim
    Victim -->|3. Cookieを自動付与してリクエスト送信| Backend

2. CSRFが成立する2つの前提条件#

CSRFはパスワードが盗まれたり暗号が解読されたりする攻撃ではありません。ブラウザとサーバー間の「無条件の信頼関係」を悪用しています:

  1. ブラウザのCookie自動送信仕様(Ambient Credentials): ブラウザは指定されたドメイン宛てにHTTPリクエストを送信する際、どのWebページから送信されたかに関わらず、そのドメインに属するCookieを自動的にすべて添付します。
  2. サーバー側の送信元未検証: サーバーが「Cookieがあるかどうか(ログイン中か)」だけを確認し、「このリクエストは本当に自社サイトの画面から送信されたか」を検証していない。

3. XSS(クロスサイトスクリプティング)との決定的な違い#

開発者が最も混同しやすいのがXSSとCSRFの違いです:

項目XSS (Cross-Site Scripting)CSRF (Cross-Site Request Forgery)
攻撃の本質標的サイト内で不正なJavaScriptを実行させる他のサイトから標的サイトへ不正なリクエストを送信させる
Cookieの窃取可能(HttpOnly がない場合、Cookieを直接盗み出す)不可能(Cookieの中身は見えず、送信を中継させるだけ)
原因入力値のエスケープ漏れやサニタイズ不備ブラウザのCookie自動添付仕様への無対策

[!NOTE] XSSはあなたの「身分証を盗む」攻撃であり、CSRFはあなたの「身分証を勝手に使って注文書を出す」攻撃です。

4. 代表的な攻撃シナリオ#

シナリオ 1: <img> タグを使ったGETリクエスト攻撃#

状態を変更する処理が不適切にHTTP GET で実装されている場合:

evil.com/page.html
<!-- ブラウザが画像を読み込もうとして、Cookie付きでGETリクエストを自動送信 -->
<img src="https://bank.com/api/transfer?to=attacker&amount=100000" width="0" height="0" />
html

シナリオ 2: 非表示フォームによる自動POSTリクエスト#

HTTP POST を使用していても、悪意あるサイト上に隠しフォームを用意し、JavaScriptで自動送信(Auto-submit)させることが可能です:

evil.com/attack.html
<form id="csrfForm" action="https://social.com/api/delete-account" method="POST">
  <input type="hidden" name="confirm" value="true" />
</form>

<script>
  // ページ読み込みと同時に自動送信
  document.getElementById('csrfForm').submit();
</script>
html

5. 包括的なCSRF防御策#

1. CSRFトークン(Synchronizer Token Pattern)#

最も伝統的かつ確実な防御手法です:

  • サーバーはユーザーのセッションごとに、推測不可能なランダム文字列(CSRFトークン)を発行します。
  • 画面を表示する際、フォームのhiddenフィールドやヘッダーにトークンを埋め込みます。
  • 同一生成元ポリシー(SOP)により、別ドメインの悪意あるサイトはこのトークンを読み取ることができないため、偽造リクエストはサーバー側で破棄されます。
transfer.html
<form action="/transfer" method="POST">
  <!-- サーバーが生成した秘密のCSRFトークン -->
  <input type="hidden" name="_csrf" value="d9a8f7b2c3e4..." />
  <input type="number" name="amount" />
  <button type="submit">送金する</button>
</form>
html

2. Cookieの SameSite 属性(モダンブラウザ標準の盾)#

Cookieの SameSite 属性を設定することで、クロスサイトリクエスト時のCookie送信挙動を制御できます:

  • SameSite=Strict: 他のサイトからのリクエストには一切Cookieを添付しない(最高水準の安全性。外部サイトのリンクから遷移した場合は未ログイン状態になる)。
  • SameSite=Lax (現代の主要ブラウザのデフォルト): POSTや画像埋め込みなどのクロスサイトリクエストではCookieを送信しない。リンクのクリックなど安全なトップレベル遷移時のみ送信を許可。
  • SameSite=None: 常にCookieを送信(Secure 属性が必須)。
Set-Cookie: sessionId=xyz123; Path=/; Secure; HttpOnly; SameSite=Lax
http

[!TIP] 認証Cookieに SameSite=Lax(または Strict)と SecureHttpOnly を付与するだけで、基本的なCSRF攻撃の大半を無効化できます。

サーバー側でセッション状態を保持しないアーキテクチャの場合:

  1. サーバーはランダムなトークンを通常のCookie(例: XSRF-TOKEN)として発行します。
  2. フロントエンド(React、Vue等)はJavaScriptでこのCookieを読み取り、カスタムHTTPヘッダー(例: X-XSRF-TOKEN)にセットして送信します。
  3. サーバー側でヘッダーの値とCookieの値が一致しているかを検証します。

4. Origin / Referer / Sec-Fetch-Site ヘッダーの検証#

サーバーサイドのミドルウェアで、リクエストの送信元が許可されたドメインと一致するかを検査します:

middleware/csrfCheck.js
export function verifyOrigin(req, res, next) {
  const origin = req.headers['origin'] || req.headers['referer'];
  const allowedHost = 'https://mybank.com';

  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    if (!origin || !origin.startsWith(allowedHost)) {
      return res.status(403).json({ error: 'CSRF Protection: Invalid Request Origin' });
    }
  }
  next();
}
javascript

6. モダンSPAとJWTにおけるCSRF#

  • JWTをメモリ上に保持し、Authorization: Bearer <token> ヘッダー経由で送信するSPA構成では、ブラウザが勝手にヘッダーを付与することはないため、CSRF攻撃は成立しません。
  • 一方で、利便性のためにJWTをCookieに保存している場合は、従来のセッションCookieと同様にCSRF対策が必須となります。

7. まとめ#

CSRFは、「ブラウザの仕組みに対する無条件の過信」が生み出す脆弱性です。

安全なWebアプリケーションを構築するための原則:

  1. 状態を変更する操作には必ず POST / PUT / DELETE を使用する。
  2. すべての認証Cookieに SameSite=Lax または SameSite=Strict を設定する。
  3. 重要なフォームやAPIには CSRFトークン またはカスタムヘッダーを導入する。
  4. バックエンドで Origin ヘッダーの検証を行う。

参考文献#