# CSRF（クロスサイトリクエストフォージェリ）とは？仕組みと対策

CSRFの仕組みと攻撃が成立する条件、CSRFトークン・Fetch Metadata・SameSite属性による対策を、MDN・OWASP・IPAの資料をもとにNode.jsのコード例つきでWeb開発者向けに解説します。

CSRF（クロスサイトリクエストフォージェリ）は、ログイン中のユーザーのブラウザに、別のサイトから意図しないリクエストを送らせる攻撃です。ブラウザがCookieを自動で付けて送るため、サーバーは本人の操作だと誤認して送金や設定変更を実行してしまいます。対策の基本は、状態を変えるリクエストにCSRFトークンやFetch Metadataの確認を組み込むことで、SameSite属性は補助として重ねます。

<Points>

- Cookieだけで認証し、推測できるパラメータだけで処理を実行するサイトはCSRFの影響を受ける
- 主な対策はCSRFトークン、Fetch Metadata（`Sec-Fetch-Site`）、単純リクエストにしない設計のいずれか
- SameSite属性は多層防御の一部。GETで状態を変えない、XSSを作らないことが前提になる

</Points>

## CSRFとは

MDNによると、次の3つの条件がそろうとCSRFが成立します。

<Figure title="CSRFが成立する条件" source="MDN「Cross-site request forgery (CSRF)」">

| 条件 | 具体例 |
| --- | --- |
| HTTPリクエストでサーバーの状態を変える | 送金、パスワードやメールアドレスの変更、投稿、退会 |
| 認証をCookieだけに頼っている | セッションCookieが付いていれば本人の操作とみなす |
| リクエストのパラメータを攻撃者が推測できる | 宛先と金額だけで送金が実行される |

</Figure>

OWASP Top 10:2025では、CSRF（CWE-352）はA01「Broken Access Control」に含まれています。IPAの「安全なウェブサイトの作り方」でも、CSRFは取り上げられている11種類の脆弱性のひとつです。

## 攻撃が成立する仕組み

攻撃者は、罠のページに標的サイトへ送信するフォームを仕込みます。ユーザーが標的サイトにログインしたまま罠のページを開くと、フォームが自動送信されます。

```html
<!-- 攻撃者のページ（例） -->
<form action="https://my-bank.example.org/transfer" method="POST">
  <input type="hidden" name="recipient" value="attacker" />
  <input type="hidden" name="amount" value="1000" />
</form>
<script>
  document.querySelector("form").submit();
</script>
```

状態を変える処理をGETで受け付けていると、フォームすら不要です。`<img>` タグの `src` に送金用のURLを書くだけで、ページを開いた瞬間にリクエストが送られます。

<Flow
  title="CSRF攻撃の流れ"
  steps={[
    { label: "ユーザーが標的サイトにログイン", note: "ブラウザにセッションCookieが残る" },
    { label: "罠のページを開く", note: "メールやSNSのリンクなどから" },
    { label: "隠しフォームが自動送信", note: "ブラウザがCookieを付けて送る" },
    { label: "標的サイトが処理を実行", note: "本人の操作と区別できない" },
  ]}
/>

XSSと違い、攻撃者はレスポンスの中身を読むことはできません。そのかわり、送金や設定変更のような「実行されたら困る処理」を本人の権限で実行させられます。

## 主な対策（CSRFトークン）

もっとも基本的な対策は、推測できないトークンをページに埋め込み、状態を変えるリクエストではトークンが一致した場合だけ処理する方法です。IPAも根本的解決として、秘密の値をhiddenパラメータに入れてPOSTで送る方法を挙げています。

<Steps>
<Step title="トークンを生成してセッションに保存する">

暗号論的に安全な乱数で、推測できない値を作ります。

</Step>
<Step title="フォームのhiddenフィールドに埋め込む">

GETのURLに含めると、Refererヘッダーなどから漏れるおそれがあります。POSTのボディやカスタムヘッダーで送ります。

</Step>
<Step title="受信時にセッションの値と照合する">

一致しない、または送られてこないリクエストは拒否します。

</Step>
</Steps>

Node.js（Express）での実装例です。実際のプロジェクトでは、フレームワークに組み込まれたCSRF対策機能があればそちらを使ってください。

```js
import crypto from "node:crypto";

// フォーム表示時：トークンを生成してセッションに保存する
app.get("/settings", (req, res) => {
  const token = crypto.randomBytes(32).toString("hex");
  req.session.csrfToken = token;
  res.render("settings", { csrfToken: token });
});

// 送信時：セッションの値と一致するか確認する
app.post("/settings", (req, res) => {
  const sent = Buffer.from(String(req.body.csrfToken ?? ""));
  const expected = Buffer.from(String(req.session.csrfToken ?? ""));
  if (
    expected.length === 0 ||
    sent.length !== expected.length ||
    !crypto.timingSafeEqual(sent, expected)
  ) {
    return res.status(403).send("Forbidden");
  }
  // ここで設定を更新する
  res.redirect("/settings");
});
```

```html
<form action="/settings" method="POST">
  <input type="hidden" name="csrfToken" value="（サーバーで埋め込んだトークン）" />
  <!-- 入力項目 -->
</form>
```

ステートレスなアプリでは、OWASPはサーバー側の秘密鍵でセッションに紐づく値をHMAC署名する「署名付きダブルサブミットCookie」を推奨しています。Cookieの値を照合するだけの素朴な方式は、Cookieを注入されると破られるため推奨されていません。

## Fetch Metadataと単純リクエストの回避

最近のブラウザは、リクエストがどこから来たかを示す `Sec-Fetch-Site` ヘッダーを送ります。値は `same-origin`、`same-site`、`cross-site`、`none`（ユーザーが直接開いた場合）のいずれかです。

```js
// クロスサイトからの状態変更を拒否する
app.post("/transfer", (req, res) => {
  const site = req.headers["sec-fetch-site"];
  if (site !== "same-origin" && site !== "same-site") {
    return res.status(403).send("Forbidden");
  }
  // ここで送金処理を行う
});
```

OWASPは、`Sec-Fetch-Site` を主な判断材料にしつつ、ヘッダーがない場合は `Origin`、次に `Referer` で確認する方法を示しています。オリジンは完全一致で照合し、`null` を許可リストに入れないよう注意しています。この方式の前提として、アプリ全体をHTTPSにしておく必要があります。

`fetch()` で呼ぶAPIでは、リクエストを「単純リクエスト」にしない方法も有効です。`Content-Type: application/json` やカスタムヘッダーを付けると、クロスオリジンでの送信にはプリフライトが必要になり、既定ではブロックされます。

```js
fetch("/api/transfer", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ recipient: "joe", amount: "100" }),
});
```

<Compare>
<Column title="フォーム（HTMLの送信）">

- CSRFトークンをhiddenフィールドで送る
- Fetch Metadataでクロスサイトを拒否
- 重要な操作は再認証を求める

</Column>
<Column title="JavaScriptから呼ぶAPI" tone="accent">

- JSONやカスタムヘッダーで単純リクエストにしない
- CORSで信頼するオリジンだけを許可
- Fetch Metadataでクロスサイトを拒否

</Column>
</Compare>

ただしMDNは、サーバーのCORS設定が送信元のオリジンに対して `Access-Control-Allow-Origin` と `Access-Control-Allow-Credentials` の両方を返すと、この保護が無効になると注意しています。サブドメインをワイルドカードで許可するようなCORS設定も避けてください。

## SameSite属性とその他の対策

Cookieの `SameSite` 属性は、クロスサイトのリクエストにCookieを付けるかどうかを制御します。OWASPは、主要なブラウザが既定で `SameSite=Lax` として扱っていると説明していますが、古いブラウザや組み込みブラウザでは適用されない場合があるとも書いています。

<Figure title="SameSite属性の値と注意点">

| 値 | 挙動 | 注意点 |
| --- | --- | --- |
| `Strict` | クロスサイトのリクエストには一切Cookieを付けない | 外部リンクから来たときにログインしていない状態に見えることがある |
| `Lax` | クロスサイトでは、安全なメソッド（GETなど）のトップレベル遷移のときだけ付ける | GETで状態を変える処理があると防げない。メソッドオーバーライドにも注意 |

</Figure>

```http
Set-Cookie: session=...; Secure; HttpOnly; SameSite=Strict
```

MDNは、状態を変える操作に使うCookieには `Strict`、ログイン済みの画面を表示するかどうかの判定に使うCookieには `Lax` を推奨しています。SameSiteは「オリジン」ではなく「サイト」単位で判定されるため、同じサイトのサブドメインはすべて信頼することになる点も覚えておきたいところです。

このほか、IPAは次の対策も挙げています。

- 重要な処理の直前にパスワードの再入力を求める
- Refererを確認し、正しいページからのリクエストだけを処理する（Refererを送らない設定の利用者は使えなくなる）
- 重要な操作が行われたら登録メールアドレスに通知する（攻撃を防ぐものではないが、早く気づける）

<Note title="要追記：実体験">（ここに自社で使っているフレームワークのCSRF対策機能の設定方法や、Fetch Metadataを導入したときの影響範囲の調査結果を書く）</Note>

## よくある質問

### Q. SameSite=Laxが既定なら、CSRF対策は不要ですか？

不要にはなりません。OWASPもMDNも、SameSiteは多層防御のひとつで、単独では十分ではないとしています。Laxではトップレベル遷移のGETにはCookieが付くうえ、古いブラウザでは既定の挙動が適用されない場合もあります。CSRFトークンやFetch Metadataと組み合わせてください。

### Q. なぜGETで状態を変えてはいけないのですか？

GETのリクエストは `<img>` タグやリンクだけで送らせることができ、`SameSite=Lax` でもトップレベル遷移ならCookieが付いてしまうためです。MDNもOWASPも、状態を変える処理にGETを使わないよう明記しています。まずはセッションCookieに依存して状態を変えるリクエストをすべて洗い出し、POSTなどに寄せてから対策を入れてください。

### Q. XSSがあるとCSRF対策はどうなりますか？

OWASPは、XSSがあるとすべてのCSRF対策が無効化され得ると警告しています。同じオリジンで動くスクリプトは、正しいトークンやヘッダーを付けてリクエストを送れるためです。[XSSの対策](/xss/)もあわせて確認してください。

### Q. APIをトークン認証（Authorizationヘッダー）にしている場合は？

MDNは、Cookieだけで認証していることをCSRFの成立条件に挙げています。ブラウザが自動で付けない認証情報だけで認証しているなら、この条件には当てはまりません。ただし、Cookieと併用している場合は対策が必要です。

出典: [MDN「Cross-site request forgery (CSRF)」](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF)、[OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)、[IPA「安全なウェブサイトの作り方 1.6 CSRF」](https://www.ipa.go.jp/security/vuln/websecurity/csrf.html)、[IPA「安全なウェブサイトの作り方」](https://www.ipa.go.jp/security/vuln/websecurity/about.html)、[OWASP A01:2025 Broken Access Control](https://top10.owasp.org/2025/A01_2025-Broken_Access_Control/)
