CSRF・SameSite・CSP とXSS対策
概要
Cookie ベース認証(BFF パターン や HTTP-only Cookie 認証)は、トークンをブラウザに露出させない 強力な方式ですが、クッキーが自動送信される性質ゆえに、次の2つの脅威への対策が必須になります。
- CSRF(クロスサイトリクエストフォージェリ) — 自動送信されるクッキーの悪用
- XSS(クロスサイトスクリプティング) — 注入スクリプトによるセッションの乗っ取り(セッションライディング)
本ドキュメントは、SameSite 属性・アンチフォージェリトークン・CSP(Content Security Policy) という3つの防御を、Cookie 認証の文脈で整理します。
CSRF(Cross-Site Request Forgery)とは
CSRF は、ログイン済みユーザーのブラウザに、攻撃者サイトから意図しないリクエストを送らせる攻撃です。 クッキーは送信先オリジンに対して自動付与されるため、攻撃者のページからの POST であっても、被害者の セッションクッキーが一緒に送られてしまいます。
Bearer トークンを Authorization ヘッダで明示的に付与する方式は、
ヘッダがクロスサイトで自動付与されないため CSRF に暗黙の耐性があります。一方、Cookie 認証は明示的な
CSRF 対策が必須です。
防御 1:SameSite 属性
クッキーの SameSite 属性は、クロスサイトリクエスト時にクッキーを送るかどうかを制御します。
| 値 | 挙動 | 用途 |
|---|---|---|
Strict | クロスサイトでは一切送信しない | 最も安全。外部リンク経由の初回遷移でも送られない点に注意 |
Lax | トップレベルの GET ナビゲーションのみ送信 | 既定値。利便性と安全性のバランス |
None | 常に送信(Secure 必須) | クロスサイト埋め込みが必要な場合のみ |
SameSite=Strict は CSRF に対する強力な第一防御線ですが、単独では不十分とされます(ブラウザ差異、
将来的な仕様変更、同一サイト内の経路など)。多層防御としてアンチフォージェリトークンを併用します。
SameSite=Strict を認証クッキーに使う場合でも、OIDC のコールバック(IdP からのトップレベル遷移)で使う
相関(correlation)クッキーは別途 SameSite=None 等が必要になることがあります。ASP.NET Core の OIDC
ハンドラはこれらを自動で扱いますが、独自実装時は注意してください。
防御 2:アンチフォージェリ(CSRF)トークン
サーバーが発行する予測不能なトークンを、状態変更リクエスト(POST/PUT/DELETE/PATCH)で検証する方式です。 代表的なパターンは2つあります。
- 同期トークンパターン:サーバーがセッションに紐づくトークンを保持し、リクエストのトークンと照合。
- ダブルサブミットクッキー:トークンをクッキーとヘッダの両方で送り、一致を検証。
HttpOnly なシークレットと、JS 可読なリクエストトークンの分離
ASP.NET Core のアンチフォージェリは、内部的に2つの要素から成ります。
- シークレットを保持するクッキー(例
__Host-csrf)→HttpOnlyにすべき(JS から読ませない)。 - リクエストトークン(クライアントが
X-CSRF-TOKENヘッダで返す値)→ SPA が読めなければならない。
アンチフォージェリのクッキーを HttpOnly にするのは正しい設定です。しかし、SPA は HttpOnly クッキーを
読めないため、別途 JS 可読なリクエストトークン用クッキー(例 XSRF-TOKEN、HttpOnly=false)を発行
する必要があります。HttpOnly を外して回避するのは誤りです。ASP.NET Core では
IAntiforgery.GetAndStoreTokens() でリクエストトークンを発行できます。
// 安全(GET)レスポンスで JS 可読なリクエストトークンを発行
app.Use(async (ctx, next) =>
{
if (HttpMethods.IsGet(ctx.Request.Method))
{
var tokens = antiforgery.GetAndStoreTokens(ctx);
ctx.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!, new CookieOptions
{
HttpOnly = false, // SPA が読む必要があるのはこちら
Secure = true,
SameSite = SameSiteMode.Strict
});
}
await next();
});
// SPA 側:XSRF-TOKEN を読み、X-CSRF-TOKEN ヘッダで送る
export function csrfInterceptor(req, next) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const token = getCookie('XSRF-TOKEN'); // HttpOnly でないクッキー
if (token) req = req.clone({ headers: req.headers.set('X-CSRF-TOKEN', token) });
}
return next(req);
}
ASP.NET Core の UseAntiforgery() は、アンチフォージェリのメタデータを持つエンドポイントのみ自動検証
します。リバースプロキシ(例 YARP)のプロキシエンドポイントはメタデータを持たないため、/api/**
への unsafe メソッドでは自動検証が発火しない場合があります。プロキシ前に
IAntiforgery.ValidateRequestAsync() を明示的に呼ぶ必要があります。
防御 3:XSS と CSP(Content Security Policy)
トークン窃取 vs セッションライディング
Cookie + HttpOnly はトークンの**窃取(持ち出し)**を防ぎますが、XSS そのものは防ぎません。
注入スクリプトはクッキーを読めなくても、同一オリジンからのリクエストでクッキーが自動送信されるため、
被害者のセッションで API を呼べてしまいます(セッションライディング)。
したがって XSS の根本対策が必須であり、その中核が CSP です。
CSP の主要ディレクティブ
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-<random>'; # インラインスクリプトは nonce/hash のみ許可
object-src 'none';
base-uri 'self';
frame-ancestors 'none'; # クリックジャッキング対策
| ディレクティブ | 役割 |
|---|---|
default-src 'self' | 既定で同一オリジンのみ許可 |
script-src | 実行可能なスクリプト元を限定。'unsafe-inline'/'unsafe-eval' は避け、nonce/hash を使う |
object-src 'none' | <object>/<embed> を禁止 |
base-uri 'self' | <base> 改ざんによるリンク乗っ取りを防止 |
frame-ancestors 'none' | 他サイトからの iframe 埋め込みを禁止 |
併用すべきセキュリティヘッダ
| ヘッダ | 役割 |
|---|---|
Strict-Transport-Security (HSTS) | HTTPS を強制し、ダウングレード/中間者攻撃を防ぐ |
X-Content-Type-Options: nosniff | MIME スニッフィングを無効化 |
Referrer-Policy: no-referrer | リファラ経由の情報漏えいを防ぐ |
X-Frame-Options: DENY | 古いブラウザ向けのクリックジャッキング対策(CSP frame-ancestors の補完) |
まとめ
- Cookie 認証は自動送信されるため、CSRF 対策が必須。
- SameSite=Strict(第一防御)+ アンチフォージェリトークン(多層防御)を併用する。
- アンチフォージェリは「HttpOnly のシークレット」と「JS 可読なリクエストトークン」を分離する。
- リバースプロキシ経路では CSRF 検証が漏れやすいので明示的に検証する。
HttpOnlyクッキーはトークン窃取を防ぐが XSS は防がない → CSP と XSS 対策が必須。