awesome-hacks
Docs

CSRF攻撃の基礎と対策

Cross-Site Request Forgeryの仕組みと、CSRFトークン・SameSite Cookieによる防御方法を理解する

最終更新:2026/06/16

TL;DR

  • CSRFは、ログイン済みユーザーのブラウザを経由して、攻撃者が意図したリクエストを勝手に送らせる攻撃
  • ブラウザはリクエスト先のサイトに対してCookieを自動で付与するため、被害者が意図せず操作を実行させられる
  • 主な対策は CSRFトークンSameSite Cookie属性 の2つ

CSRFとは

CSRF(Cross-Site Request Forgery)は、攻撃者が用意した罠サイトやリンクを通じて、被害者のブラウザからターゲットサイトへ不正なリクエストを送らせる攻撃。

ブラウザは同一ドメインのCookieを自動で付与してリクエストを送る仕様になっている。これを悪用することで、被害者が意図せずログイン済みの別サービスへリクエストを送ることになる。


攻撃の流れ

典型的なシナリオ

1. 被害者が bank.example.com にログインしている(セッションCookieが存在する)
2. 攻撃者が用意した悪意あるサイト evil.example.com を被害者が開く
3. 悪意あるページに埋め込まれたフォームやimgタグが自動で発火する
4. ブラウザが bank.example.com への送金リクエストをCookie付きで送信する
5. サーバーは正規のセッションCookieを確認し、リクエストを受け付けてしまう

攻撃コードの例

<!-- 攻撃者のページに埋め込まれたフォーム(自動送信) -->
<form action="https://bank.example.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker-account" />
  <input type="hidden" name="amount" value="100000" />
</form>
<script>document.forms[0].submit();</script>
<!-- GETリクエストが副作用を持つ場合はimgタグでも攻撃できる -->
<img src="https://bank.example.com/transfer?to=attacker&amount=100000" />

被害者が evil.example.com を開いた瞬間に、bank.example.com への送金リクエストが自動送信される。サーバー側はCookieが正規のものであるため、正当なリクエストと区別できない。


XSSとの違い

混同しやすいので整理しておく。

CSRFXSS
攻撃の起点別サイト(クロスサイト)から同一サイトに悪意あるスクリプトを埋め込む
被害被害者が意図しない操作を実行させられる攻撃者のスクリプトが被害者のブラウザ上で動く
Cookieへの直接アクセスできないできる(httpOnly でない場合)

CSRFはリクエストを「送らせる」攻撃で、Cookieの「読み取り」はできない。


対策1: CSRFトークン

サーバーがランダムなトークンをセッションに紐づけて発行し、フォームやAPIリクエストにそのトークンを含めることを要求する方法。

仕組み

1. サーバーがセッションにCSRFトークンを保存し、レスポンスに含める
2. クライアントはリクエスト送信時にそのトークンをヘッダー or フォームに含める
3. サーバーはリクエストのトークンとセッションのトークンを照合する
4. 攻撃者は被害者のCSRFトークンを知らないため、有効なリクエストを偽造できない

攻撃者が罠サイトからリクエストを送っても、正しいCSRFトークンを含められないため弾かれる。

NestJSでの実装例(csurf)

npm install csurf cookie-parser
// main.ts
import * as cookieParser from "cookie-parser";
import * as csurf from "csurf";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(cookieParser());
  app.use(
    csurf({
      cookie: {
        httpOnly: true,
        sameSite: "strict",
      },
    })
  );

  await app.listen(3000);
}
// csrf.controller.ts
import { Controller, Get, Req, Res } from "@nestjs/common";
import { Request, Response } from "express";

@Controller("csrf")
export class CsrfController {
  @Get("token")
  getToken(@Req() req: Request, @Res() res: Response) {
    // クライアントに渡すトークンを生成(csurfが req.csrfToken() を付与する)
    res.json({ csrfToken: req.csrfToken() });
  }
}
// フロントエンド側(例: fetch)
const { csrfToken } = await fetch("/csrf/token").then((r) => r.json());

await fetch("/transfer", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRF-Token": csrfToken, // ← ヘッダーにトークンを付与
  },
  body: JSON.stringify({ to: "someone", amount: 1000 }),
});

対策2: SameSite Cookie属性

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

動作
Strict別サイトからのリクエストには一切Cookieを送らない
Lax別サイトからのGETナビゲーション(リンククリック)にはCookieを送るが、POSTには送らない
None制限なし(Secure 属性が必須)
// NestJSでセッションCookieにSameSiteを設定する例
res.cookie("session", token, {
  httpOnly: true,
  secure: true,      // HTTPS必須(SameSite: NoneはSecureが必須)
  sameSite: "lax",   // 多くのケースで推奨
});

LaxとStrictの使い分け

  • Strict: セキュリティは最も強いが、外部サイトからのリンクでセッションが引き継がれない(ログイン済みなのに未ログインになる)
  • Lax: GETナビゲーションはCookieを送るため、ユーザー体験を損なわずCSRF(主にPOSTでの攻撃)を防げる

多くの場合 Lax が現実的なバランスとなる。


対策3: OriginヘッダーとRefererヘッダーの検証

ブラウザはリクエスト時に Origin または Referer ヘッダーを自動付与する。サーバー側でこれらを確認し、予期しないオリジンからのリクエストを弾く方法。

// NestJSのミドルウェアでOriginを検証する例
@Injectable()
export class CsrfOriginGuard implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const allowedOrigins = ["https://app.example.com"];
    const origin = req.headers["origin"] ?? req.headers["referer"];

    if (req.method !== "GET" && !allowedOrigins.some((o) => origin?.startsWith(o))) {
      throw new ForbiddenException("Invalid origin");
    }

    next();
  }
}

ただしOrigin/Refererは省略されるケースもあるため、これ単独ではなくCSRFトークンやSameSiteとの組み合わせが推奨。


SPAとCSRF

React/Next.jsなどのSPAでAPIをJWTで認証している場合、トークンをどこに保存するかによって状況が変わる。

localStorageにJWTを保存している場合

ブラウザはクロスサイトリクエスト時にlocalStorageの内容を自動送信しない。そのため、認証にCookieを使わないSPAはCSRFの影響を受けにくい。

ただしXSSに弱い(JavaScriptからlocalStorageを読み取れる)。

httpOnly CookieにJWTを保存している場合

JavaScriptからCookieを読み取れないためXSSに強いが、ブラウザが自動でCookieを送るためCSRF対策が必要になる。

保存場所          XSSリスク   CSRFリスク
localStorage      高          低(Cookie未使用)
httpOnly Cookie   低          高(要CSRF対策)

httpOnly Cookieを使うなら SameSite: LaxSameSite: Strict を必ず設定する。


まとめ

対策有効なケース
CSRFトークンセッションベース認証・フォームベースのアプリ
SameSite Cookieモダンブラウザ環境(現在の主流対策)
Originヘッダー検証APIサーバーでの補完的な対策

現在のモダンブラウザでは SameSite: Lax がデフォルトになっている場合も多く、これだけで多くのCSRF攻撃を防げる。ただし、セッションベースの認証では明示的な設定とCSRFトークンの組み合わせを推奨する。


関連