CSRF (Cross-Site Request Forgery) は、被害者がログイン中の Web アプリに、攻撃者が別ドメインに用意した仕掛けから意図しないリクエストを送らせる脆弱性。本質は 「ブラウザは Cookie を自動的に付ける」 という仕様と 「サーバは Cookie の存在をもって本人とみなす」 という実装のミスマッチ。XSS とよく混同されるが別物で、XSS はスクリプト実行、CSRF はリクエスト送信。本稿では発生メカニズム / GET-POST-JSON の 3 形態 / Samy・ルータ書き換え事件 / トークン・SameSite Cookie・Origin 検証による多層防御まで通しで扱う。
CSRF とは何か — Cookie 自動付与の悪用 #
ブラウザは Cookie を オリジン (scheme + host + port) に紐付け、そのオリジンへの全リクエストに自動的に付与する。これがリクエスト元のページがどのオリジンであるかとは無関係に行われるのが CSRF の起点。
(1) 被害者が対象サイトにログイン中 (セッション Cookie を保持) (2) 対象サイトが「Cookie の存在 = 本人」と判定する (3) 対象サイトのエンドポイントが、攻撃者が予測可能な形式のリクエストで状態変更できる。この 3 つが揃うと、別ドメインからの「攻撃用ページの読み込み」だけで攻撃が成立する。
最小の攻撃例 #
<!-- attacker.example/lure.html — 別ドメイン -->
<img src="https://bank.example/transfer?to=attacker&amount=1000000">
# 被害者が bank.example にログイン状態でこのページを開くと:
# ブラウザは <img> の src を GET で取りに行く
# その GET に bank.example のセッション Cookie が自動付与される
# bank.example のサーバは「ログイン済みユーザからの送金指示」と判定 → 実行
被害者は 画像が表示されないくらいで何が起きたか気付かない。GET でお金を動かす API はその時点で SQLi 並みに致命的、というのが教訓。
XSS との違い #
| 観点 | XSS | CSRF |
|---|---|---|
| 何をする | スクリプト実行 (任意コード) | リクエスト送信 (定形操作) |
| 動く場所 | 被害サイトのオリジン内 | 攻撃者サイト上 |
| 読み取り | できる (Cookie 以外 / DOM / API レスポンス) | できない (SOP で response 読めない) |
| 影響範囲 | ほぼ無制限 | 状態変更系 API に限定 |
| Cookie 属性 | HttpOnly が効く | HttpOnly は効かない (送信に介入しないので) |
XSS が「スクリプト実行」、CSRF が「リクエスト送信」。「XSS でできる代用品 = CSRF」と言われることもあるが、XSS は CSRF トークンも読めるので XSS が成立した時点で CSRF 対策は無力化される。
OWASP Top 10 における位置づけ #
CSRF は 2003〜2017 年まで OWASP Top 10 の常連 (A8) だったが、2017 年版で除外された。理由は SameSite Cookie やフレームワーク標準の CSRF 対策の普及。ただし 2025 年現在もレガシーシステム・カスタム API・WebView 経由のアプリで頻繁に再発見されており、「死んだ脆弱性」ではない。
攻撃の 3 形態 #
CSRF はリクエスト形式によって 3 つに分かれる。それぞれ対策の有効度が異なる。
GET-based — 最も簡単で最も致命的 #
<img src>, <script src>, <iframe src>, <link>, window.open などはすべて GET を発生させる。ユーザ操作なしで攻撃者サイトに被害者が訪問しただけで発火する。「副作用のある GET」(送金・退会・パスワード変更を GET で受ける) は完全な設計ミス。
POST-based — フォーム自動送信 #
<form> の action を他オリジンに向けて、JavaScript で自動 submit する。
<form id="x" action="https://bank.example/transfer" method="POST">
<input name="to" value="attacker">
<input name="amount" value="1000000">
</form>
<script>document.getElementById('x').submit();</script>
# ページが読み込まれた瞬間に POST が飛ぶ → 自動付与される Cookie で認証通過
Content-Type: application/x-www-form-urlencoded や multipart/form-data は「単純リクエスト」扱いされ、CORS のプリフライト無しで送信できる。これが防げない原因。
JSON / Custom-Header based — CORS で守られている領域 #
fetch で Content-Type: application/json を付けたり、X-CSRF-Token のような独自ヘッダを付けると、ブラウザは事前に OPTIONS プリフライトを投げ、サーバが許可しなければ本リクエストを送らない。
つまり API が JSON しか受け付けない設計なら、それ自体が CSRF の予防になる。逆に「便利だから」と Form エンコードでも JSON でも受け付ける実装にすると CSRF の入口を残してしまう。
サブカテゴリ: Login CSRF #
被害者を攻撃者のアカウントでログインさせ、その後の操作 (検索履歴・支払い情報入力等) を攻撃者アカウントに蓄積させる攻撃。Google や Yahoo でも過去に発生。「ログインそのものも CSRF 対策が必要」という反直感的な脆弱性。
攻撃のシナリオ — 攻撃者の動き #
被害者の操作は「攻撃ページを開く」だけ。これが XSS のような「入力欄に攻撃文字列を打ち込む」と違って、インシデント発覚が遅れる最大の理由。
著名な CSRF 事件 #
Samy Worm の CSRF 部分 (MySpace, 2005) #
XSS で有名な Samy ワームの中身は実は CSRF とも組み合わさっていた。「感染者が攻撃者を友達追加する」「自分のプロフィール HTML を書き換える」という操作はすべて MySpace の状態変更 API への CSRF として実装され、被害者ブラウザの正規 Cookie で動いていた。XSS と CSRF を組み合わせた典型例。
家庭用ルータの設定書き換え (2008〜) #
被害者宅のルータ管理画面 (http://192.168.1.1/) は LAN 内からしかアクセスできないが、被害者ブラウザ自身は LAN 内側にいる。攻撃者が用意した Web ページが <img src="http://192.168.1.1/setup?dns=8.8.8.x"> を仕込み、被害者の家庭で DNS 設定を攻撃者の悪意ある DNS に書き換える。以降、被害者宅の全端末で bankexample.com のアクセスがフィッシングサイトに誘導される。Linksys / D-Link / TP-Link 等の多数モデルが該当し、ブラジル・コロンビアでは数百万台規模で書き換えられたと報告された。LAN 内デバイスへの CSRF という応用形。
Netflix (2006) #
Netflix 旧サイトに大量の CSRF 脆弱性があり、他人のキューに DVD を追加、配送先住所を変更、アカウント情報変更などが攻撃者の用意したページから可能だった。当時の研究者 Dave Ferguson 氏が公開し、Netflix は数週間で全 API に CSRF トークンを導入。CSRF が「学術的関心」から「現実的脅威」に格上げされた事件のひとつ。
YouTube (2008) #
YouTube に「動画にコメントする」「友達追加」「お気に入りに登録」「動画を共有する」など、ほぼ全ての状態変更操作で CSRF が成立した。Princeton 大学の研究で公表され、SOP と Cookie の根本ミスマッチが大規模サービスでも放置されている実例として広く引用された。
ING Direct (2008) #
オンライン銀行 ING Direct で 資金移動と新規口座開設 が CSRF で可能だったことが公表された。金融機関での CSRF が広く認知される転換点になり、以降、銀行系では CSRF トークン + 取引パスワード + SMS 認証の多層化が標準化した。
教訓 #
事件群に共通するのは、「CSRF は地味だが、状態変更 API を1 個でも守り忘れると被害が大きい」点。全 API の防御を 100% にしないと意味がない性質の脆弱性で、SameSite Cookie のような「Cookie 層での自動防御」がここまで標準化されたのはこの教訓の積み重ね。
防御策 — 多層防御 #
SameSite Cookie — 現代の主役 #
Cookie に SameSite 属性を付けると、ブラウザは別オリジンから発生したリクエストへの Cookie 付与を制限する。
| 値 | 挙動 |
|---|---|
Strict |
別オリジンからのリクエストには 一切送らない。リンクから飛んできた場合 (top-level navigation) も含む |
Lax |
GET の top-level navigation だけ送る。POST や <img> <iframe> には送らない |
None |
全部送る (旧来動作)。必ず Secure 必須 |
Chrome は 2020 年 (バージョン 80) から SameSite=Lax をデフォルトにした (明示されていない Cookie の暗黙の挙動)。これにより、CSRF 対策が「何もしていない」サイトでも、Lax のデフォルトによって POST CSRF の大半が自動的に防がれるようになった。CSRF が OWASP Top 10 から外れた最大の理由。
ブラウザのデフォルトに依存しない。Safari は SameSite=Lax 相当をデフォルトにしない時期があった、組み込みブラウザ・WebView・古いブラウザは未対応の可能性もある。必ず Set-Cookie で明示する。
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/Strict は最も安全だが「外部リンクから来たユーザがログアウト状態に見える」という UX 課題があるため、認証 Cookie は Lax、決済確定など特に重要な Cookie は Strict の使い分けが定石。
CSRF トークン (Synchronizer Token Pattern) — 古典で確実 #
サーバ側でランダム値を生成してフォーム/レスポンスに埋め込み、リクエスト時にも同じ値を送らせる。攻撃者の別オリジンページからは正規トークンが読めない (Same-Origin Policy が守る) ため、リクエストを正しく組み立てられない。
<form method="POST" action="/transfer">
@csrf {{-- を生成 --}}
<input name="to">
<input name="amount">
</form>
# サーバ側 (Middleware VerifyCsrfToken) が _token を session のトークンと照合
| FW | トークン機構 |
|---|---|
| Laravel | @csrf, VerifyCsrfToken middleware (デフォルト ON) |
| Django | {% csrf_token %}, CsrfViewMiddleware |
| Rails | csrf_meta_tags, protect_from_forgery (デフォルト ON) |
| Spring Security | CsrfFilter (デフォルト ON) |
| Express | csurf (ただし 2022 年に deprecated。csrf-csrf が代替) |
**SPA / API 系では Cookie に トークンを入れて、JS で読み出してヘッダにつける「Double Submit Cookie Pattern」**が一般的。Cookie は同一オリジンの JS なら読めるが、別オリジンの JS は SOP で読めない。
Origin / Referer ヘッダ検証 #
ブラウザがリクエストに自動的に付ける Origin ヘッダ (POST 等) や Referer を検証し、自サイト以外からのリクエストを弾く。
# PHP
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!in_array($origin, ['https://app.example'])) {
http_response_code(403);
exit;
}Origin は攻撃者の JS から偽装できない。Referer は古い Privacy 設定で省略されることがあるため、Origin を主、Referer を副で使う。
Custom Header の要求 (X-Requested-With 等) #
X-Requested-With: XMLHttpRequest のような独自ヘッダを必須にすると、別オリジンからは CORS プリフライトが必要になるため、サーバが許可していない限りリクエストが届かない。JSON API でよく使われるシンプルかつ効果的な手法。
重要操作の再認証 #
送金・パスワード変更・メアド変更などの 特に重要な操作には毎回パスワード再入力 / 多要素認証 を要求する。CSRF 対策の最終層であり、SameSite やトークンが何らかの理由で破れても被害を局所化できる。
副作用のある GET を作らない #
設計レベルでの大原則: 状態変更 API は POST / PUT / DELETE / PATCH に限定し、GET には副作用を持たせない。HTTP メソッドの仕様 (RFC 9110) でもそう定められているし、<img src> 経由の CSRF を完全に塞ぐ。
CORS との関係 — よく混同される #
「CORS を有効にすると CSRF が起きる」「CORS を厳しくすれば CSRF が防げる」はどちらも誤解。CSRF と CORS の関係を整理しておく。
CORS は JavaScript がクロスオリジンの応答を読めるかどうかを決める仕組み。読まなくていい状態変更 API でも、ブラウザはリクエスト送信自体は阻止しない。だから CORS をどんなに厳しくしても、状態変更系の CSRF は防げない。
CORS が CSRF と関わるのは、プリフライトが発生する条件のリクエスト (JSON、独自ヘッダ等) のとき。プリフライト OPTIONS で許可されないオリジンからは本リクエストが送られないため、結果として CSRF が防がれる。これは「副作用」であって CORS の目的ではない。
つまり:
- Form encoded POST → プリフライトなし → CORS は CSRF を防がない → CSRF トークン必須
- JSON POST with
Content-Type: application/json→ プリフライト発生 → CORS が許可しないオリジンは届かない → 結果的に CSRF が防がれる
API を JSON 専用にする設計が CSRF 対策として推奨されるのはこの理由。
テストと検出 #
手動テスト #
各状態変更 API について、攻撃ページを実際に作って踏ませる:
- 自サイトに通常ログイン
- 別オリジンに
<form action="https://target/api/...">を含む HTML を置く - その HTML を開いて、自サイトの状態が変わるか確認
状態変更があれば CSRF が成立している。**「成功すれば脆弱」**なテスト。Burp Suite の Generate CSRF PoC 機能が同じことを自動でやってくれる。
コードレビュー #
- 全ての状態変更 API に CSRF トークン or SameSite Cookie or Origin 検証が効いているかを網羅的に確認
- フレームワークの CSRF Middleware が特定ルートで除外されていないか (Laravel の
VerifyCsrfToken::$exceptを grep する) - API トークン認証 (Bearer) ルートは CSRF の影響を受けないが、Cookie 認証と併用すると穴が空く
自動スキャナ #
- Burp Suite Pro — 状態変更系エンドポイントへの CSRF を検出
- OWASP ZAP — CSRF トークンの欠如を検査するルールあり
- Nuclei — 設定された CSRF テンプレートで網羅検査
関連する攻撃 #
| 攻撃 | 関係 |
|---|---|
| Clickjacking | iframe の上に透明なボタンを重ねて被害者の正規クリックを別操作にすり替える。CSRF とは別だが「被害者の正規セッションを悪用」する点で類縁。X-Frame-Options / CSP frame-ancestors で防ぐ |
| SSRF | こちらは「サーバが攻撃者制御の URL に対して」リクエストを送る。CSRF が「ブラウザを踏み台」なのに対し、SSRF は「サーバを踏み台」 |
| Cross-Site Script Inclusion (XSSI) | 他オリジンから機密 JSON/JSONP を <script> で読み込んで観察。CSRF の「読めない」制約を回避する一形 |
| Login CSRF | 攻撃者のアカウントでログインさせる CSRF。OAuth 連携で特に問題化 |
| CSRF on logout | ログアウトエンドポイントへの CSRF。被害者を強制ログアウトさせて UX を破壊する DoS 系 |
まとめ — 開発者が押さえる 6 項目 #
CSRF は SameSite Cookie の標準化で大幅に減ったが、レガシー実装・WebView・組み込みブラウザ・モバイル WebView SDK などで今も発生し続けている。多層防御の発想は変わらない。
- セッション Cookie に
SameSite=Lax+HttpOnly+Secureを明示する - 状態変更 API すべてに CSRF トークンまたは Origin 検証を入れる
- 副作用のある操作を GET で受けない (RFC 9110 の原則)
- API は JSON 専用 +
application/json必須にして CORS プリフライトを発生させる - 送金・パスワード変更・メアド変更には 再認証を要求する
- FW の CSRF Middleware の 除外設定を grep して網羅的にレビュー
「Cookie が自動付与される」というブラウザの仕様自体は変えられない。だからこそ、サーバ側で「リクエストが本当に自サイトから来たか」を必ず検証する ことが CSRF 対策の核心である。