作成者: Fiyaz Hasan、Rick Anderson
クロスサイト リクエスト フォージェリは、Web ホスト アプリに対する攻撃であり、悪意のある Web アプリがクライアント ブラウザーとそのブラウザーを信頼する Web アプリとの間の対話に、影響を与える可能性があります。 これらの攻撃は、Web ブラウザーが Web サイトへのすべての要求に対して何らかの種類の認証トークンを自動的に送信するために発生する可能性があります。 この形式の悪用は、攻撃でユーザーの以前に認証されたセッションが利用されるため、"ワンクリック攻撃" または "セッション ライディング" としても知られます。 クロスサイト リクエスト フォージェリは、XSRF または CSRF とも呼ばれます。
CSRF 攻撃の例:
ユーザーが、フォーム認証を使用して
www.good-banking-site.example.comにサインインします。 サーバーはユーザーを認証し、認証 cookie を含む応答を発行します。 サイトは、有効な認証 cookie と共に受信したすべての要求を信頼するため、攻撃に対して脆弱です。ユーザーが悪意のあるサイト
www.bad-crook-site.example.comにアクセスします。悪意のあるサイト
www.bad-crook-site.example.comには、次の例のような HTML フォームが含まれています。<h1>Congratulations! You're a Winner!</h1> <form action="https://www.good-banking-site.example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click to collect your prize!" /> </form>悪意のあるサイトではなく、脆弱なサイトにフォームの
actionが POST されていることに注意してください。 これが CSRF の "クロスサイト" 部分です。ユーザーが送信ボタンを選びます。 ブラウザーは要求を行い、要求されたドメイン cookie に対する認証
www.good-banking-site.example.comを自動的に含めます。要求はユーザーの認証コンテキストを使用して
www.good-banking-site.example.comサーバー上で実行され、認証されたユーザーが実行を許可されている任意のアクションを実行できます。
ユーザーがボタンを選んでフォームを送信するシナリオに加えて、悪意のあるサイトでは次のこともできます。
- フォームを自動的に送信するスクリプトを実行する。
- フォーム送信を AJAX 要求として送信する。
- CSS を使用してフォームを非表示にする。
これらの代替シナリオでは、最初に悪意のあるサイトにアクセスすること以外に、ユーザーによる操作や入力は必要ありません。
HTTPS を使用しても CSRF 攻撃を防ぐことはできません。 悪意のあるサイトは、安全でない要求を送信するのと同じくらい簡単に、要求 https://www.good-banking-site.example.com/ を送信できます。
一部の攻撃は GET 要求に応答するエンドポイントを対象にしており、その場合、イメージ タグを使用してアクションを実行できます。 この形式の攻撃は、イメージは許可するが JavaScript はブロックするフォーラム サイトでよく使用されます。 GET 要求で状態を変更するアプリ (変数またはリソースが変更される場合) は、悪意のある攻撃に対して脆弱です。 状態を変更する GET 要求は安全ではありません。 ベスト プラクティスは、GET 要求で状態を変更しないことです。
認証に Cookie を使用する Web アプリに対して CSRF 攻撃が発生する可能性があるのは、次の理由からです。
- ブラウザーは、Web アプリによって発行された Cookie を保存します。
- 保存される Cookie には、認証されたユーザーのセッション Cookie が含まれます。
- ブラウザーは、アプリへの要求がブラウザ内でどのように生成されたかに関係なく、要求ごとにドメインに関連付けられたすべての Cookie を Web アプリに送信します。
ただし、CSRF 攻撃は Cookie の悪用に限定されません。 たとえば、基本認証やダイジェスト認証も脆弱です。 ユーザーが基本認証またはダイジェスト認証を使用してサインインすると、セッションが終了するまで、ブラウザーによって資格情報が自動的に送信されます。
このコンテキストでの "セッション" とは、その間ユーザーが認証されている、クライアント側のセッションを指します。 サーバー側のセッションや ASP.NET Coreセッション ミドルウェアとは無関係です。
ユーザーは、予防措置を講じて CSRF の脆弱性を防ぐことができます。
- Web アプリを使い終わったら、それからサインアウトします。
- ブラウザーの cookie を定期的にクリアします。
ただし、CSRF の脆弱性は、根本的に、エンド ユーザーではなく Web アプリに関する問題です。
認証の基礎
Cookie ベースの認証は、一般的な認証形式です。 トークン ベースの認証システムは普及が広がっています (特にシングルページ アプリケーション (SPA) の場合)。
Cookie ベースの認証
ユーザーがユーザー名とパスワードを使用して認証を行うと、認証チケットが含まれるトークンが発行されます。 このトークンは、認証と承認に使用できます。 トークンは、クライアントが行うすべての要求で送信される cookie として格納されます。 この cookie の生成と検証は、 cookie 認証ミドルウェアを使用して実行されます。 ミドルウェアによって、ユーザー プリンシパルは暗号化された cookie にシリアル化されます。 後続の要求では、ミドルウェアによって cookie が検証され、プリンシパルが再作成されて、そのプリンシパルが HttpContext.User プロパティに割り当てられます。
トークンベースの認証
認証されたユーザーには、トークンが発行されます (偽造防止トークンではありません)。 このトークンには、クレームまたはアプリで保持されているユーザー状態をアプリに示す参照トークンの形式で、ユーザー情報が含まれます。 ユーザーが認証を必要とするリソースにアクセスしようとすると、ベアラー トークンの形式の追加承認ヘッダーと共にトークンがアプリに送信されます。 この方法により、アプリはステートレスになります。 後続の要求ごとに、サーバー側の検証のために要求でトークンが渡されます。 このトークンは、"暗号化" されておらず、"エンコード" されています。 サーバーでは、トークンをデコードして情報にアクセスします。 後続の要求でトークンを送信するため、トークンはブラウザーのローカル ストレージに格納されます。 トークンをブラウザーのローカル ストレージに配置して取得し、ベアラー トークンとして使用すると、CSRF 攻撃に対する保護が提供されます。 ただし、XSS または侵害された外部 JavaScript ファイルを介したスクリプト インジェクションに対してアプリが脆弱な場合、攻撃者はローカル ストレージから任意の値を取得して自分自身に送信する可能性があります。 ASP.NET Core では、既定ですべてのサーバー側出力が変数からエンコードされるため、XSS のリスクが軽減されます。 Html.Raw を使用するか、信頼されていない入力でカスタム コードを使用してこの動作をオーバーライドすると、XSS のリスクが高くなる可能性があります。
トークンがブラウザーのローカル ストレージに格納される場合は、CSRF の脆弱性について心配しないでください。 CSRF が問題になるのは、トークンが cookie に格納さるときです。 詳細ついては、GitHub のイシュー「SPA code sample adds two cookies (SPA のコード サンプルで 2 つの cookie が追加される」をご覧ください。
1 つのドメインでホストされている複数のアプリ
共有ホスティング環境は、セッション ハイジャック、サインイン CSRF、その他の攻撃に対して脆弱です。
example1.contoso.net と example2.contoso.net は異なるホストですが、*.contoso.net ドメインの下のホスト間には暗黙的な信頼関係があります。 この暗黙的な信頼関係により、信頼できないホストが互いの cookie に影響を与えることができる可能性があります (AJAX 要求を管理する同一オリジン ポリシーは、必ずしも HTTP cookie に適用されるとは限りません)。
同じドメインでホストされているアプリ間で信頼された cookie を悪用する攻撃は、ドメインを共有しないことで回避できます。 各アプリが独自のドメインでホストされている場合、悪用するための暗黙的な cookie の信頼関係はありません。
メタデータヘッダーを取得
最新のブラウザーでは 、Fetch Metadata 要求ヘッダー (最も重要な Sec-Fetch-Site) がすべての要求にアタッチされます。
Sec-Fetch-Site は、要求を開始した配信元と要求元の関係を記述します。 same-origin は、サイトが自身に対して行った要求を識別し、別の配信元によって開始された要求を same-site および cross-site 識別します。
Origin ヘッダーには開始元が含まれており、Fetch Metadata より前のブラウザーのフォールバックとして機能します。
Sec-Fetch-Site および Origin は 禁止されている要求ヘッダーです。ブラウザーによって設定されます。ページで実行されている JavaScript は、それらをオーバーライドしたり偽造したりすることはできません。 これにより、サーバーが発行したトークンなしでクロスサイト要求とは別に、サイト独自の要求を伝える信頼できるシグナルになります。 ASP.NET Coreに組み込まれている自動 CSRF 保護では、このシグナルを使用して、明示的に信頼されていないクロスサイト フォームの投稿が拒否されます。
ASP.NET Coreでの CSRF の自動保護
ASP.NET Coreには、WebApplication.CreateBuilderで構築されたアプリで既定で有効になっている自動 CSRF 保護ミドルウェアが付属しています。
トークンベースの偽造防止システムとは異なり、このミドルウェアはトークンの発行や検証を行いません。 代わりに、 Sec-Fetch-Site と OriginFetch メタデータ ヘッダー を検査し、要求に対する検証の判定を記録します。 送信されたフォーム データを処理するコンポーネントは、その判定を強制し、明示的に信頼されていないクロスオリジン フォームの投稿を拒否します。
ほとんどのアプリでは、コードの変更は必要ありません。同じ配信元のブラウザー要求、安全な HTTP メソッド、ブラウザー以外のクライアント (curl、サーバー間、モバイル アプリ) はすべて影響を受けません。 ミドルウェアは主に、ブラウザーからクロスオリジン フォームの投稿を受け入れるアプリ (別の配信元の API にフォームを投稿するサイトなど) に影響します。 これらのシナリオでは、信頼された配信元を宣言するように CORS を構成するか、エンドポイントをオプトアウトする必要があります。
このミドルウェアは、トークン ベースの偽造防止システムに 追加 されます。 2 つの保護が共存し、両方とも同じエンドポイントでアクティブにすることができます。 それぞれの適用時期の比較については、「 トークンベースの偽造防止との相互作用」を参照してください。
どのように機能するのか
すべての要求について、ミドルウェアは規則の短いチェーンを評価して、許可 または拒否の判定に達 します。 チェックは順番に実行され、最初の一致が優先されます。
-
安全な HTTP メソッドは常に許可されます。
GET、HEAD、OPTIONS、およびTRACE要求がパススルーされます。 これは RFC 9110 §9.2.1 に従い、エンドポイントがGETの状態を変更してはならないという長年の規則と一致します。 -
Sec-Fetch-Site: same-originまたはSec-Fetch-Site: noneが許可されます。 最新のブラウザーは、すべての要求にSec-Fetch-Siteを送信します。same-originは通常のアプリ内ナビゲーションとフェッチをカバーし、noneはユーザーによって直接開始された要求 (ブックマークを使用して URL を入力) について説明します。 これは最も一般的なコード パスです。最も正当なブラウザー トラフィックはここで終了します。 - CORS からの信頼された配信元が許可されます。 要求に
Originヘッダーが含まれており、エンドポイントの解決された CORS ポリシーがその配信元を信頼している場合、要求は許可されます。 ミドルウェアは、CORS ミドルウェアと同じ方法でポリシーを解決します。エンドポイントごとのポリシーは、最初[EnableCors("name")]、次にAddDefaultPolicyに登録されている既定のポリシーです。 このルールの重要な制限については、 クロスオリジン クライアントの許可 を参照してください。 - その他の
Sec-Fetch-Site値は拒否されます。Sec-Fetch-Siteがcross-siteまたはsame-siteであり、配信元が CORS 経由で信頼されていない場合、要求は拒否されます。 -
Sec-Fetch-Siteはありませんが、Originが存在します。ミドルウェアは、Originと要求からビルドされたscheme://host[:port]を比較します。 一致する場合、要求は許可されます。それ以外の場合は拒否されます。 これは、Fetch Metadata 仕様 (~2020 年リリース) より古いブラウザーのフォールバック パスです。 -
Sec-Fetch-Siteなし、Originなし: 要求は許可されます。 ブラウザーは常に書き込み要求でこれらの少なくとも 1 つを送信するため、両方が欠落している要求は、ほぼ確実に、curl、Postman、モバイル アプリ、サーバー間の呼び出し元などのブラウザー以外のクライアントです。 CSRF はブラウザー専用の攻撃ベクトルであるため、これらの要求はパススルーされます。
ミドルウェアは、要求自体を終了するのではなく、要求に対してこの判定を記録します。 拒否された判定が HTTP 400 Bad Request 応答に変わる方法とタイミングについては、「 遅延検証」を参照してください。
遅延検証
ミドルウェアが単独で要求を拒否することはありません。 代わりに、要求の IAntiforgeryValidationFeature (トークンベースの偽造防止システムが使用するのと同じ機能) に対する判定を記録します。拒否された判定は 無効として記録されます。 リクエストはパイプライン内をさらに進みます。 無効な判定は、フォーム データを処理するコンポーネントによって観察された場合にのみ、HTTP 400 Bad Request になります。 この遅延は、トークンベースのシステムが既に動作する方法と一致します。判定は早期に生成されますが、フォームが使用される時点で適用されます。
次のコンポーネントは、 IAntiforgeryValidationFeature を読み取り、記録された判定が無効な場合に 400 - Bad Request 要求を拒否します。
- 偽造防止によって保護された MVC アクション。
- フォーム パラメーターをバインドする最小限の API エンドポイント。
- Blazor SSR エンドポイント。
- バックストップとして機能する要求フォームを直接読み取るコード。
各コンシューマーは、最初に、判定を信頼する前に偽造防止または CSRF ミドルウェアが実際に実行されたことを確認するため、どちらのミドルウェアも含まないパイプラインでは誤った拒否は生成されません。
このモデルの結果として、判定が無効な場合でもフォーム データを読み取らないエンドポイントが実行されます。 たとえば、JSON から本文をバインドする JSON API エンドポイントや、要求本文を無視するハンドラーは、クロスオリジン要求では自動的に拒否されません。 判定は、検査するコードの IAntiforgeryValidationFeature に記録されますが、何も適用されません。 CSRF はフォームとcookie の攻撃ベクトルであるため、ブラウザーから送信されたフォームを使用しないエンドポイントでは、通常、この拒否は必要ありません。 フォームを処理するエンドポイント (Razor Pages、MVC ビュー、 Blazor SSR、最小 API フォーム バインド) は、自動的に保護を取得します。
既定の動作
ミドルウェアは WebApplication.CreateBuilder によって自動的に登録され、認証と承認後に実行されます。 登録された ICsrfProtection 実装を使用して各要求を検証します。既定では、「 動作方法」で説明されている規則が適用されます。 既定の実装は置き換えることができます。「カスタマイズ: ICsrfProtectionを実装する」を参照してください。 ミドルウェアを完全にオフにするには、「 グローバルに無効にする」を参照してください。
その結果、次のようなフォーム処理エンドポイントを持つ最小限のアプリが既に保護されています。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/widgets", ([FromForm] Widget w) =>
Results.Created($"/widgets/{w.Id}", w));
app.Run();
同じ配信元の POST /widgets 要求を行うブラウザーが、通常どおりエンドポイントに到達します。
https://attacker.example.com のブラウザーから同じフォームを投稿すると、ハンドラー本体が実行される前に、エンドポイントでフォームがバインドされる際に 400 - Bad Request で拒否されます。
curlやSec-Fetch-Siteを含まないOrigin要求は許可されます。
拒否は フォーム コンシューマーに遅延されるため、フォーム データを読み取らないエンドポイント (JSON から本文をバインドする JSON API など) は、クロスオリジン要求でも自動的に拒否されません。 判定結果は、それを検査しようとするコードのために、引き続きリクエスト内に記録されます。
ミドルウェアは、既存の偽造防止モデルと統合されます。
-
最小限の API: エンドポイントで
.DisableAntiforgery()を呼び出すと、そのエンドポイントはトークン ベースのミドルウェアと CSRF 保護ミドルウェアの 両方 から除外されます。 両方で同じメタデータ (IAntiforgeryMetadata { RequiresValidation = false }) がチェックされます。 -
MVC コントローラーとアクション:
[IgnoreAntiforgeryToken]また、両方の保護からエンドポイントをオプトアウトします。
クロスオリジンクライアントを許可する
対応が必要になる最も一般的なシナリオは、クロスオリジンのフォームを送信するブラウザーベースのクライアントです。たとえば、https://app.contoso.com にあるサイトが https://api.contoso.com にある API にフォームを送信する場合です。 このようなフォームの投稿は、Sec-Fetch-Siteがsame-siteではなくcross-siteまたはsame-originであり、フォーム コンシューマーが400 - Bad Requestでその判定を強制するため、既定では拒否されます。
CSRF ミドルウェアでは、独自の信頼リストは導入されません。 これにより、 CORS ミドルウェア がエンドポイントに対して解決するのと同じ CORS ポリシーが再利用されます。そのポリシーで要求の Originが許可されている場合、CSRF ミドルウェアは要求に対して許可された判定を記録します。
ポリシーは、エンドポイントごとに次の順序で選択されます。
-
[EnableCors("api")](MVC) または.RequireCors("api")(最小 API) →名前付きポリシー"api"。 - エンドポイントに CORS メタデータがありません →
AddDefaultPolicyに登録された既定のポリシー。 - 一致するポリシーがない(名前付きポリシーが登録されていない、既定のポリシーがない、または
services.AddCors()が一度も呼び出されていない)→ CORS 由来の信頼なし。 ミドルウェアには、Sec-Fetch-Siteと Origin と Host の比較ルールが適用されます。
既定のポリシーと最小限の API エンドポイントを使用する最小限の例:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
policy.WithOrigins("https://app.contoso.com")
.AllowAnyHeader()
.AllowAnyMethod());
});
var app = builder.Build();
app.UseCors();
app.MapPost("/widgets", ([FromForm] Widget w) =>
Results.Created($"/widgets/{w.Id}", w));
app.Run();
単一のエンドポイントの名前付きポリシーの場合:
app.MapPost("/widgets", ([FromForm] Widget w) => Results.Created($"/widgets/{w.Id}", w))
.RequireCors("api");
警告
AllowAnyOrigin は意図的に CSRF 信頼シグナルとして受け入 れられない 。
AllowAnyOrigin は、"どのブラウザーでもこのリソースを読み取ることができる" ことを意味します。これは、"配信元がユーザーに代わって状態を変更する可能性がある" とは異なる懸念事項です。 AllowAnyOrigin を信頼済みとして扱うと、このミドルウェアはクロスオリジン書き込みの no-op になります。 パブリック読み取り CORS ポリシーと CSRF で保護された書き込みを組み合わせる必要があるアプリでは、信頼された書き込み元をWithOriginsと明示的に一覧表示するか、 ベースの認証に依存しない場合はcookieする必要があります。
[DisableCors] は CSRF オプトアウトではありません。CORS から派生した信頼ステップはスキップされ、要求は引き続き Sec-Fetch-Site と Origin-vs-Host の規則を満たす必要があります。 CSRF 保護をオプトアウトするには、「 エンドポイントのオプトアウト」を参照してください。
CORS 自体 (AddCors、AddDefaultPolicy、AddPolicy、WithOrigins、およびその他のポリシー ビルダー API) の構成の詳細については、ASP.NET Coreでのクロスオリジン要求 (CORS) の有効化に関するページを参照してください。
エンドポイントのオプトアウト設定
エンドポイントがブラウザーに到達できない場合、またはベアラー トークンや API キーなどのcookie ではないメカニズムによって保護されている場合は、ミドルウェアをグローバルに無効にするのではなく、個別にオプトアウトします。
最小限の API — エンドポイントまたはグループで DisableAntiforgery を呼び出します。
app.MapPost("/api/webhook", (WebhookPayload p) => Results.Accepted())
.DisableAntiforgery();
MVC コントローラー — アクションまたはコントローラーに [IgnoreAntiforgeryToken] を適用します。
[ApiController]
[Route("api/[controller]")]
[IgnoreAntiforgeryToken]
public class WebhookController : ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] WebhookPayload payload) => Accepted();
}
どちらの方法でも、エンドポイントに IAntiforgeryMetadata { RequiresValidation = false } が追加されます。これは、CSRF ミドルウェアが検証をスキップすることによって優先します。
警告
エンドポイントでの CSRF 保護の無効化は、エンドポイントが CSRF 攻撃に対して脆弱でない場合にのみ行う必要があります。たとえば、ブラウザーから呼び出しできないエンドポイントや、ベアラー トークンや API キーなどのcookie 以外の認証で保護されているエンドポイントなどです。 認証に Cookie に依存するブラウザーからアクセスできるエンドポイントでは、CSRF 保護を無効にしないでください。
グローバルに無効にする
ミドルウェアは、 DisableCsrfProtection 構成キーを使用してアプリ全体で無効にすることができます。 これはエスケープ ハッチであり、エンドポイントごとのオプトアウトを優先します。
appsettings.jsonの場合:
{
"DisableCsrfProtection": true
}
または、環境変数として:
ASPNETCORE_DisableCsrfProtection=true
このキーが true に設定されている場合、 WebApplication はパイプラインへのミドルウェアの登録をスキップします。
ICsrfProtection サービスは登録されたままであるため、直接解決されるものは引き続き機能します。
警告
自動 CSRF ミドルウェアは、アプリが app.UseAntiforgery()を呼び出さない場合でも、検証を必要とするエンドポイントの偽造防止要件も満たします。 アプリが偽造防止に依存しているが、 app.UseAntiforgery()を呼び出さない場合、CSRF ミドルウェアをグローバルに無効にするか、ミドルウェアが挿入されていない WebApplicationでビルドされていないホストで実行すると、それらのエンドポイントには偽造防止ミドルウェアは残りません。 そのようなエンドポイントにリクエストすると、例外がスローされます。 その構成で app.UseAntiforgery() を呼び出します。
ブラウザーのサポート
Sec-Fetch-Site は、Chromium ベースのブラウザー、Firefox、Safari のすべての現在のバージョンでサポートされています。 権限のある互換性テーブルについては、Sec-Fetch-Siteの MDN リファレンスを参照してください。
Fetch Metadata より前の古いブラウザーでは、 Sec-Fetch-Site送信されません。 これらのクライアントの場合、ミドルウェアは、要求のスキームとホストに対する Origin ヘッダーの比較にフォールバックします。 ブラウザーは長年にわたってクロスオリジン書き込み要求に対して Origin を送信してきたため、このフォールバックは基本的にすべてのレガシ ブラウザー トラフィックを対象としています。
ブラウザー以外のクライアント (curl、Postman、モバイル アプリ、サーバー間の呼び出し元) は、通常、 Sec-Fetch-Site も Originも送信しません。 CSRF はブラウザー専用の攻撃ベクトルであり、ブラウザーによって Cookie などのアンビエント資格情報が自動的にアタッチされるため、これらの要求は許可されます。 API を攻撃するブラウザー以外のクライアントには、CSRF は必要ありません。API は、所有している資格情報を使用して直接呼び出すことができます。
カスタマイズ: 実装 ICsrfProtection
決定ロジックは、1 つのメソッド インターフェイスの背後に存在します。
namespace Microsoft.AspNetCore.Antiforgery;
public interface ICsrfProtection
{
ValueTask<CsrfProtectionResult> ValidateAsync(HttpContext context);
}
既定の実装を置き換えるには、DI にシングルトンを登録します。 フレームワークは TryAddSingletonを使用するため、明示的な AddSingleton 呼び出しによって既定値がオーバーライドされます。
builder.Services.AddSingleton<ICsrfProtection, AllowlistCsrfProtection>();
カスタム実装は、信頼モデルが CORS に適合しない場合 (たとえば、パートナーの配信元の固定許可リストが優先される場合や、より厳密な規則が必要な場合など) に役立ちます。
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
public sealed class AllowlistCsrfProtection : ICsrfProtection
{
private static readonly HashSet<string> SafeMethods =
new(StringComparer.OrdinalIgnoreCase) { "GET", "HEAD", "OPTIONS", "TRACE" };
private static readonly HashSet<string> TrustedOrigins =
new(StringComparer.OrdinalIgnoreCase)
{
"https://app.contoso.com",
"https://admin.contoso.com",
};
public ValueTask<CsrfProtectionResult> ValidateAsync(HttpContext context)
{
if (SafeMethods.Contains(context.Request.Method))
{
return ValueTask.FromResult(CsrfProtectionResult.Allowed());
}
var origin = context.Request.Headers.Origin.ToString();
var allowed = !string.IsNullOrEmpty(origin) && TrustedOrigins.Contains(origin);
return ValueTask.FromResult(
allowed ? CsrfProtectionResult.Allowed() : CsrfProtectionResult.Denied());
}
}
ミドルウェアは、登録されている実装に関係なく .DisableAntiforgery() / [IgnoreAntiforgeryToken] を受け入れ、 ValidateAsync が呼び出される前にミドルウェア自体によってオプトアウトが処理されます。
トークンベースのアンチフォージェリとの連携
2 つの CSRF 防御は、異なるレイヤーをターゲットとし、共存するように設計されています。 また、両者は同じリクエスト機能を共有しています。つまり、どちらもその結果を IAntiforgeryValidationFeature に記録し、フォームのコンシューマーはそこにある判定をそのまま適用します。
| 特徴 | トークンベース AntiforgeryMiddleware |
自動 CSRF 保護ミドルウェア |
|---|---|---|
| 導入された | ASP.NET Core 2.0 以降 | .NET 11 |
| アクティベーション |
app.UseAntiforgery()経由でのオプトイン (またはAddMvc / MapRazorPages / AddRazorComponentsによって暗黙的に) |
WebApplication.CreateBuilder によって自動挿入 |
| 有効性を確認 | 同期トークン (フォーム フィールド + cookie ペア) |
Sec-Fetch-Site
/
Origin ヘッダー |
| Requires | トークン暗号化の ASP.NET Coreデータ保護の概要 | トークンなし、状態なし |
| ブラウザーのスコープ | Cookie を送信するすべてのブラウザー | すべての最新ブラウザー;レガシー向けの Origin フォールバック |
| エンドポイントごとのオプトアウト | .DisableAntiforgery() / [IgnoreAntiforgeryToken] |
同じ — 両方が同じメタデータを受け入れ |
トークンベースのミドルウェアは、悪意のあるサイトがユーザーのアンビエント Cookie を使用して脆弱なサイトに POST フォームをトリガーする従来の CSRF 攻撃パターンから特に保護します。 自動 CSRF ミドルウェアは、ブラウザーによって提供されるメタデータを使用して、HTTP レイヤーで同じ脅威に対処します。 どちらも同じエンドポイントでアクティブにすることができ、多くのアプリは多層防御の恩恵を受けることができます。
- トークン システムを既に使用している Razor Pages、MVC、Blazor SSR アプリでは、トークン フローを変更することなく、トークン検証の前に実行されるヘッダーベースのチェックが取得されます。
- フォームをバインドする最小限の API アプリは、エンドポイントを介して
app.UseAntiforgery()またはスレッドIAntiforgeryを呼び出す必要なく、便利な既定値を取得します。 - クロスオリジン SPA から呼び出される API は 、CORS 許可リストと組み合わされたこのミドルウェアに依存でき、API が HTML フォームを処理しない場合はトークン システムを完全にスキップできます。
自動 CSRF ミドルウェアは、両方とも同じフォーム処理エンドポイントを保護するため、多くのシナリオでトークンベースのシステムを置き換えます。 トークン ベースのシステムは、次の場合に保持します。
- アプリは、
Sec-Fetch-Siteを送信しないブラウザーをサポートする必要があります。 ブラウザーのサポートを参照してください。 - アプリは IAntiforgeryAdditionalDataProvider を使用して、トークン内の余分なデータをラウンドトリップします。
- セキュリティ レビューまたはコンプライアンス要件では、トークンの防御を独立したレイヤーとして指定します。
フォーム統合、AJAX フロー、AntiforgeryOptions経由の構成、IAntiforgery API など、トークン ベースのシステムの詳細については、ASP.NET Coreの偽造防止に関するページを参照してください。
トークンの検証が優先されます
アプリが app.UseAntiforgery()を呼び出すと、トークンベースのミドルウェアは自動 CSRF ミドルウェアの後で実行されます。 トークン ミドルウェアは、記録された CSRF ミドルウェアの判定をクリアし、トークン検証の結果に置き換えます。 トークンの結果が正式な情報です:
- CSRF ミドルウェアが無効とマークされた要求は、有効なトークンを持つ場合に有効になります。
- CSRF ミドルウェアが許可する要求は、トークンが見つからないか無効である場合は無効としてマークされます。
この順序付けとは、トークン システムを使用するアプリは、自動ミドルウェアが存在する前と同じエンド ツー エンドの動作を見ることを意味し、トークンを使用しないアプリは CSRF ミドルウェアの判定にフォールバックします。
Blazor サーバー側の静的レンダリング
Blazor 静的サーバー側レンダリング (SSR) エンドポイントは、同じ遅延モデルに参加します。
Razor コンポーネント エンドポイントは、アップストリーム ミドルウェアによってIAntiforgeryValidationFeatureに記録された判定を信頼し、その判定が無効な場合にのみフォーム投稿の400 - Bad Requestを返します。 エンドポイントは要求自体を検証しなくなりました。
動作は、実行されたミドルウェアによって異なります。
-
app.UseAntiforgery()を呼び出すアプリは変更されません。 トークンベースのミドルウェアは各要求を検証し、以前と同様にレンダリングされたフォームに対して偽造防止トークンが生成されます。 -
app.UseAntiforgery()を呼び出さないアプリは、代わりに自動 CSRF ミドルウェアによって保護されます。 この構成では、後の要求でトークンを検証するためのトークン ミドルウェアが存在しないため、エンドポイントは偽造防止トークンの生成をスキップします。
これは、以前に app.UseAntiforgery()を削除した静的 SSR の動作の変更です。保護されていないままではなく CSRF ミドルウェアによって保護され、偽造防止トークンの出力を停止します。 移行ガイダンスについては、「.NET 10 の ASP.NET Core から .NET 11 の ASP.NET Coreへの移行」を参照してください。 正式な破壊的変更のお知らせについては、Blazorサーバー側レンダリングではアンチフォージェリ検証がミドルウェアに委ねられますを参照してください。
Troubleshooting
症状: ブラウザーからの同じ配信元要求は成功しますが、クロスオリジン フォームの投稿では本文のない 400 - Bad Request が返されます。
原因: CSRF ミドルウェアは、クロスオリジン要求の無効な判定と、MVC アクション、最小限の API フォーム バインド、 Blazor SSR フォームポストなどのフォーム処理コンポーネントを記録し、その判定を 400 - Bad Requestで強制しました。 これは、フォームを処理するエンドポイントで想定される既定の動作です。
解像 度: シナリオに応じて、次のいずれかを選択します。
- 呼び出し元が既知で信頼されている場合は、 CORS 経由で許可します。
- エンドポイントがブラウザーに到達できない場合、またはcookie以外の認証を使用している場合は、または
.DisableAntiforgery()で[IgnoreAntiforgeryToken]します。 - アプリ全体 (移行期間中など) をオプトアウトする必要がある場合は、 グローバルに無効にします。
診断:ミドルウェアは、カテゴリ DebugのMicrosoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware レベルですべての無効な判定をイベント名CsrfValidationFailedと共にログに記録します。
Debugでそのカテゴリのappsettings.Development.jsonログ記録を有効にします。
{
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware": "Debug"
}
}
}
記録された判定がログに次のように表示されます。
dbug: Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware[1]
Cross-origin CSRF protection marked request POST /widgets from origin 'https://attacker.example.com' as invalid.
ローカルでの再現:フォーム エンドポイントに対するクロスオリジン ブラウザー要求をシミュレートするには、明示的なcurl ヘッダーでOriginを使用します。
curl -i -X POST https://localhost:{PORT}/widgets \
-H "Origin: https://attacker.example.com" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=test"
{PORT}をアプリのローカル HTTPS ポートに置き換えます。 エンドポイントがフォームをバインドし、記録された判定を強制するため、 400 - Bad Request が観察されます。 何も判定を読み取っていないため、フォーム以外のエンドポイントは通常の応答を返します。
Origin ヘッダーがない場合、curlはSec-Fetch-Siteも送信せず、どちらのヘッダーも含まない要求はブラウザー以外のクライアントとして扱われるため、同じ要求が許可されます。
この記事の残りの部分で説明するトークン ベースの偽造防止システムは、このミドルウェアより前のものであり、引き続き使用できます。 ほとんどのアプリでは、自動保護で十分です。 トークン ベースのシステムを維持するタイミングと移行方法については、「.NET 10 の ASP.NET Coreから .NET 11 の ASP.NET Coreへの移行」を参照してください。
ASP.NET Core での偽造防止
警告
ASP.NET Core では、ASP.NET Core データ保護を使用して偽造防止が実装されています。 サーバー ファームで動作するように、データ保護スタックを構成する必要があります。 詳しくは、データ保護の構成に関する記事をご覧ください。
次のいずれかの API が
詳細については、「最小限の API を使用した偽造防止」を参照してください。
FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。 Razor ファイルの次のマークアップによって、偽造防止トークンが自動的に生成されます。
<form method="post">
<!-- ... -->
</form>
同様に、フォームのメソッドが GET でない場合は、IHtmlHelper.BeginForm によって既定で偽造防止トークンが生成されます。
HTML フォーム要素に対する偽造防止トークンの自動生成は、<form> タグに method="post" 属性が含まれていて、次のいずれかが当てはまる場合に行われます。
- action 属性が空です (
action="")。 - action 属性が指定されていません (
<form method="post">)。
HTML フォーム要素に対する偽造防止トークンの自動生成は、無効にすることができます。
偽造防止トークンを明示的に無効にするには、
asp-antiforgery属性を使います。<form method="post" asp-antiforgery="false"> <!-- ... --> </form>タグ ヘルパーの ! オプトアウト シンボルを使うと、フォーム要素がタグ ヘルパーからオプトアウトされます。
<!form method="post"> <!-- ... --> </!form>ビューから
FormTagHelperを削除します。FormTagHelperビューに次のディレクティブを追加することで、ビューから Razor を削除できます。@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages は XSRF/CSRF から自動的に保護されます。 詳細しくは、「XSRF/CSRF および Razor ページ」をご覧ください。
CSRF 攻撃を防ぐ最も一般的な方法は、シンクロナイザー トークン パターン (STP) を使用することです。 STP は、ユーザーがフォーム データを含むページを要求するときに使われます。
- サーバーは、現在のユーザーの ID に関連付けられているトークンをクライアントに送信します。
- クライアントは、検証のためにトークンをサーバーに送り返します。
- 認証されたユーザーの ID と一致しないトークンをサーバーが受信した場合、要求は拒否されます。
トークンは一意で、予測できません。 トークンは、一連の要求の適切な順序を保証するためにも使用できます (たとえば、ページ 1 > ページ 2 > ページ 3 という要求の順序を保証します)。 ASP.NET Core MVC と Razor Pages のテンプレートのすべてのフォームで、偽造防止トークンが生成されます。 次の 2 つのビューの例では、偽造防止トークンが生成されます。
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
タグ ヘルパーと HTML ヘルパー <form> を使用せず、偽造防止トークンを @Html.AntiForgeryToken 要素に明示的に追加します。
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
いずれの場合も、ASP.NET Core によって次の例のような非表示のフォーム フィールドが追加されます。
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core には、偽造防止トークンを使用するための 3 つのフィルターが含まれています。
AddControllers での偽造防止
AddControllers を呼び出しても、偽造防止トークンは有効に "なりません"。 組み込みの偽造防止トークンをサポートするには AddControllersWithViews を呼び出す必要があります。
複数のブラウザー タブとシンクロナイザートークンパターン
複数のタブに別々のユーザーとしてログインする動作や、1 つのタブに匿名でログインする動作はサポートされていません。
AntiforgeryOptions を使用して偽造防止を構成する
アプリのAntiforgeryOptions ファイル内のProgramをカスタマイズします。
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
次の表に示すように、cookie クラスのプロパティを使って偽造防止の CookieBuilder プロパティを設定します。
| 回答内容 | 説明 |
|---|---|
| Cookie | 偽造防止 cookie の作成に使用される設定を決定します。 |
| FormFieldName | ビューで偽造防止トークンをレンダリングするために偽造防止システムによって使用される非表示フォーム フィールドの名前。 |
| HeaderName | 偽造防止システムによって使用されるヘッダーの名前。
null の場合、システムではフォーム データのみが考慮されます。 |
| SuppressXFrameOptionsHeader |
X-Frame-Options ヘッダーの生成を抑制するかどうかを指定します。 既定では、ヘッダーは値 "SAMEORIGIN" を使用して生成されます。 既定値は false です。 |
一部のブラウザーでは、セキュリティで保護されていないエンドポイントが "セキュリティで保護された" フラグを使用して Cookie を設定したり、'secure' フラグが設定されている Cookie を上書きしたりすることはできません (詳細については、「セキュリティで 保護されていない配信元からの 'secure' Cookie の変更の非推奨」を参照してください)。 セキュリティで保護されたエンドポイントと安全でないエンドポイントを混在させるのはアプリの一般的なシナリオであるため、ASP.NET Core では、cookieのcookieをSecurePolicyに設定することで、偽造防止CookieSecurePolicy.Noneなどの一部の Cookie に対するセキュリティで保護されたポリシーの制限を緩和します。 悪意のあるユーザーが偽造防止 cookieを盗んだ場合でも、通常はフォーム フィールド (より一般的) または別の要求ヘッダー (あまり一般的ではない) と認証 cookieを介して送信される偽造防止トークンも盗む必要があります。 認証または承認に関連する Cookie は、 CookieSecurePolicy.Noneよりも強力なポリシーを使用します。
必要に応じて、アプリのcookie ファイルで次のDevelopmentプロパティ設定を使用して、HTTPS 経由でのみ Secure Sockets Layer (SSL) を使用して、AntiforgeryOptions.Cookie以外の環境で偽造防止Programをセキュリティで保護できます。
if (!builder.Environment.IsDevelopment())
{
builder.Services.AddAntiforgery(o =>
{
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
}
詳細については、「CookieAuthenticationOptions」を参照してください。
IAntiforgery を使用して偽造防止トークンを生成する
IAntiforgery では、偽造防止機能を構成するための API が提供されます。
IAntiforgery は Program.cs で WebApplication.Services を使用してリクエストできます。 次の例では、アプリのホーム ページのミドルウェアを使用して偽造防止トークンを生成し、応答で cookieとして送信します。
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
前の例では、cookie という名前の XSRF-TOKEN を設定しています。 クライアントはこの cookie を読み取り、その値を AJAX 要求にアタッチされたヘッダーとして提供できます。 たとえば、Angular には、既定で
偽造防止の検証を要求する
ValidateAntiForgeryToken アクション フィルターは個々のアクションやコントローラーに対して、またはグローバルに適用できます。 このフィルターが適用されているアクションに対して行われた要求は、要求に有効な偽造防止トークンが含まれていない限り、ブロックされます。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
ValidateAntiForgeryToken 属性には、HTTP GET 要求を含む、マークするアクション メソッドへの要求に対するトークンが必要です。
ValidateAntiForgeryToken 属性がアプリのコントローラー全体に適用されている場合は、IgnoreAntiforgeryToken 属性でオーバーライドできます。
安全でない HTTP メソッドについてのみ偽造防止トークンを自動的に検証する
ValidateAntiForgeryToken 属性を広範に適用してから IgnoreAntiforgeryToken 属性でオーバーライドするのではなく、AutoValidateAntiforgeryToken 属性を使用できます。 この属性は、ValidateAntiForgeryToken 属性と同じように動作しますが、次の HTTP メソッドを使用して行われた要求についてはトークンを要求しない点が異なります。
- GET
- HEAD
- オプション
- TRACE
非 API のシナリオでは、AutoValidateAntiforgeryToken を広範に使うことをお勧めします。 この属性により、POST アクションが既定で保護されるようになります。 代わりの方法は、ValidateAntiForgeryToken が個々のアクション メソッドに適用されない限り、既定で偽造防止トークンを無視することです。 このシナリオでは、POST アクション メソッドが誤って保護されないままになり、アプリが CSRF 攻撃に対して脆弱になる可能性が高くなります。 すべての POST で、偽造防止トークンを送信する必要があります。
API には、トークンの cookie 以外の部分を送信するための自動メカニズムはありません。 実装は、クライアント コードの実装によって異なる場合があります。 いくつかの例を次に示します。
クラスレベルの例:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
グローバルな例:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
グローバルまたはコントローラーの偽造防止属性をオーバーライドする
IgnoreAntiforgeryToken フィルターは、特定のアクション (またはコントローラー) に対して偽造防止トークンを不要にするために使用されます。 このフィルターを適用すると、より高いレベル (グローバルまたはコントローラー上) で指定されている ValidateAntiForgeryToken および AutoValidateAntiforgeryToken フィルターがオーバーライドされます。
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
認証後にトークンを更新する
ユーザーが認証された後、ユーザーをビューまたは Razor Pages ページにリダイレクトすることによって、トークンを更新する必要があります。
JavaScript、AJAX、SPA
従来の HTML ベースのアプリでは、偽造防止トークンは非表示のフォーム フィールドを使用してサーバーに渡されます。 最新の JavaScript ベースのアプリと SPA では、多くの要求がプログラムによって行われます。 これらの AJAX 要求では、要求ヘッダーや cookie などの他の手法を使ってトークンを送信できます。
cookie が、認証トークンの格納と、サーバーでの API 要求の認証に使用されている場合、CSRF が問題になる可能性があります。 ローカル ストレージがトークンの格納に使われている場合は、ローカル ストレージの値はすべての要求でサーバーに自動的に送信されないため、CSRF の脆弱性が軽減される可能性があります。 ローカル ストレージを使用してクライアントに偽造防止トークンを格納し、要求ヘッダーとしてトークンを送信することをお勧めします。
Blazor
詳細については、「ASP.NET Core の Blazor 認証と認可」を参照してください。
JavaScript
JavaScript とビューを使うと、ビュー内からサービスを使ってトークンを作成できます。 ビューに IAntiforgery サービスを挿入し、GetAndStoreTokens を呼び出します。
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
前の例では、JavaScript を使用して、AJAX POST ヘッダーの非表示フィールドの値を読み取っています。
この方法を使用すると、サーバーからの cookie の設定やクライアントからの読み取りを、直接処理する必要がなくなります ただし、IAntiforgery サービスを挿入できない場合は、JavaScript を使用して cookie のトークンにアクセスします。
- サーバーへの追加要求でトークンにアクセスします (通常は
same-origin)。 - cookie のコンテンツを使用して、トークンの値を含むヘッダーを作成します。
スクリプトで、X-XSRF-TOKEN という名前の要求ヘッダーでトークンを送信することが想定されている場合は、X-XSRF-TOKEN ヘッダーを検索するように偽造防止サービスを構成します。
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
次の例では、要求トークンを書き込む保護されたエンドポイントを、JavaScript で読み取り可能な cookie に追加します。
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
次の例では、JavaScript を使用して AJAX 要求を行い、トークンを取得して、適切なヘッダーを使って別の要求を行います。
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
Note
要求ヘッダーとフォーム ペイロードの両方で偽造防止トークンが提供される場合、ヘッダー内のトークンのみが検証されます。
Minimal API を使用した偽造防止
AddAntiforgery および UseAntiforgery(IApplicationBuilder) を呼び出して、DI に偽造防止サービスを登録します。 偽造防止トークンは、クロスサイト 要求 フォージェリ攻撃 を軽減するために使用されます。
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
app.MapGet("/", () => "Hello World!");
app.Run();
偽造防止ミドルウェア:
- 要求パイプラインの残りの部分の実行について、ショートサーキット処理は行われません。
- 現在の要求の IAntiforgeryValidationFeature に HttpContext.Features を設定します。
偽造防止トークンは、次の場合にのみ検証されます:
- エンドポイントに、IAntiforgeryMetadata の
RequiresValidation=trueを実装するメタデータが含まれている。 - エンドポイントに関連付けられている HTTP メソッドは、POST、PUT、または PATCH 型の関連 する HTTP メソッド です。
- 要求は有効なエンドポイントに関連付けられています。
偽造防止ミドルウェアは、要求パイプラインをショートサーキットしません。 トークンの検証が失敗した場合でも、エンドポイント コードは常に実行されます。 トークン検証の結果を確認するには、IAntiforgeryValidationFeatureからHttpContext.Featuresを解決し、IsValid プロパティまたは Error プロパティでエラーの詳細を調べます。 この方法は、失敗した偽造防止検証に対してエンドポイントでカスタム処理が必要な場合に便利です。
注: 手動で有効にした場合、ユーザーが認証されていないときにフォーム データを読み取らないように、認証ミドルウェアと承認ミドルウェアの後に偽造防止ミドルウェアを実行する必要があります。
既定では、フォーム データを受け入れる最小限の API では、偽造防止トークンの検証が必要であり、偽造防止の検証が成功しない場合は、アプリケーション コードを実行する前に失敗します。
次の GenerateForm メソッドを考えてみましょう:
public static string GenerateForm(string action,
AntiforgeryTokenSet token, bool UseToken=true)
{
string tokenInput = "";
if (UseToken)
{
tokenInput = $@"<input name=""{token.FormFieldName}""
type=""hidden"" value=""{token.RequestToken}"" />";
}
return $@"
<html><body>
<form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
{tokenInput}
<input type=""text"" name=""name"" />
<input type=""date"" name=""dueDate"" />
<input type=""checkbox"" name=""isCompleted"" />
<input type=""submit"" />
</form>
</body></html>
";
}
上記のコードには、アクション、偽造防止トークン、および bool トークンを使用する必要があるかどうかを示す 3 つの引数があります。
次の例を考えてみましょう:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
// Pass token
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo", token), "text/html");
});
// Don't pass a token, fails
app.MapGet("/SkipToken", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo",token, false ), "text/html");
});
// Post to /todo2. DisableAntiforgery on that endpoint so no token needed.
app.MapGet("/DisableAntiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo2", token, false), "text/html");
});
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
app.Run();
class Todo
{
public required string Name { get; set; }
public bool IsCompleted { get; set; }
public DateTime DueDate { get; set; }
}
public static class MyHtml
{
public static string GenerateForm(string action,
AntiforgeryTokenSet token, bool UseToken=true)
{
string tokenInput = "";
if (UseToken)
{
tokenInput = $@"<input name=""{token.FormFieldName}""
type=""hidden"" value=""{token.RequestToken}"" />";
}
return $@"
<html><body>
<form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
{tokenInput}
<input type=""text"" name=""name"" />
<input type=""date"" name=""dueDate"" />
<input type=""checkbox"" name=""isCompleted"" />
<input type=""submit"" />
</form>
</body></html>
";
}
}
上記のコードで、次に投稿すると:
-
/todo有効な偽造防止トークンが必要です。 -
/todo2へのポストでは、 が呼び出されるため、有効な偽造防止トークンは。
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
警告
.DisableAntiforgery()を呼び出すと、エンドポイントのクロスサイト リクエスト フォージェリ (CSRF) 保護が無効になります。 これは、エンドポイントが次のような CSRF 攻撃に対して脆弱でない場合にのみ使用する必要があります。
- ブラウザーから呼び出しできないエンドポイント (内部 API など)
- cookieベース以外の認証で保護されたエンドポイント (ベアラー トークンや API キーなど)
- ユーザー Cookie に依存しない内部エンドポイントまたはインフラストラクチャ エンドポイント
認証のために Cookie に依存する、またはユーザーが送信したフォーム データを処理するブラウザーでアクセス可能なエンドポイントの偽造防止検証を無効に しないでください 。これにより、アプリケーションが CSRF 攻撃に対して公開されるためです。
ポストの結果はそれぞれ次のようになります。
-
/todoエンドポイントによって生成されたフォームから/が成功するのは、偽造防止トークンが有効であるためです。 -
/todoは、/SkipTokenによって生成されたフォームに偽造防止機能が含まれていないため、失敗します。 -
/todo2は、偽造防止が必要ないため、/DisableAntiforgeryエンドポイントによって生成されたフォームから正常に実行されます。
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
フォームが有効な偽造防止トークンなしで送信された場合:
-
Development環境では、例外が発生します。 -
Production環境では、メッセージがログに記録されます。
HTTP メソッドの制限事項と HttpMethodOverrideMiddleware の相互作用
ミドルウェアベースの偽造防止パスでは、AntiforgeryMiddleware と UseAntiforgery() は、HTTP POST、PUT、および PATCH 要求に対してのみ、偽造防止トークンを検証します。
DELETE などの他の HTTP メソッドは自動的には検証されません。
他の HTTP メソッドの偽造防止トークンを検証するには、DI から IAntiforgery を解決して、ValidateRequestAsync または IsRequestValidAsync を明示的に呼び出します。
app.MapDelete("/item/{id}", async (int id, IAntiforgery antiforgery, HttpContext context) =>
{
await antiforgery.ValidateRequestAsync(context);
// Process the DELETE request
});
警告
HttpMethodOverrideMiddlewareがFormFieldName (フォーム フィールド モード) で構成され、AntiforgeryMiddlewareの前に配置されている場合、POST 要求を DELETE (または検証されていない別のメソッド) にオーバーライドできます。
AntiforgeryMiddlewareは POST、PUT、PATCH のみを検証するため、オーバーライドされた要求は偽造防止検証をバイパスします。
これらのエンドポイントを保護するには:
- パイプラインで許可されている場合は、偽造防止検証の後に
HttpMethodOverrideMiddlewareを配置することをお選びください。 - 偽造防止の検証に依存するエンドポイントでは、フォーム フィールドのオーバーライドを避けます。
- フォーム フィールドのオーバーライドを最初に実行する必要がある場合は、
IAntiforgery.ValidateRequestAsyncを使用して明示的に偽造防止トークンを検証します。
構成の詳細については、ミドルウェア ASP.NET Core参照してください。
Windows 認証と偽造防止 cookie
Windows 認証を使用するときは、cookie の場合と同じ方法で、アプリケーション エンドポイントを CSRF 攻撃から保護する必要があります。 ブラウザーによって認証コンテキストがサーバーに暗黙的に送信されるため、エンドポイントを CSRF 攻撃から保護する必要があります。
偽造防止を強化する
IAntiforgeryAdditionalDataProvider 型を使用すると、開発者は、トークンごとに追加データをラウンドトリップすることで、CSRF 対策システムの動作を強化できます。 フィールド トークンを生成するたびに GetAdditionalData メソッドを呼び出し、戻り値を生成されるトークンに埋め込みます。 実装者は、タイムスタンプ、nonce、または他の値を返し、トークンの検証時に ValidateAdditionalData を呼び出してこのデータを検証できます。 クライアントのユーザー名は生成されたトークンに既に埋め込まれているので、この情報を含める必要はありません。 トークンに補足データが含まれていても、IAntiForgeryAdditionalDataProvider が構成されていない場合は、補足データは検証されません。
追加のリソース
クロスサイト リクエスト フォージェリ (XSRF または CSRF とも呼ばれます) は、Web ホスト アプリに対する攻撃であり、悪意のある Web アプリがクライアント ブラウザーとそのブラウザーを信頼する Web アプリとの間の対話に、影響を与える可能性があります。 これらの攻撃は、Web ブラウザーが Web サイトへのすべての要求に対して何らかの種類の認証トークンを自動的に送信するために発生する可能性があります。 この形式の悪用は、攻撃でユーザーの以前に認証されたセッションが利用されるため、"ワンクリック攻撃" または "セッション ライディング" としても知られます。
CSRF 攻撃の例:
ユーザーが、フォーム認証を使用して
www.good-banking-site.example.comにサインインします。 サーバーはユーザーを認証し、認証 cookie を含む応答を発行します。 サイトは、有効な認証 cookie と共に受信したすべての要求を信頼するため、攻撃に対して脆弱です。ユーザーが悪意のあるサイト
www.bad-crook-site.example.comにアクセスします。悪意のあるサイト
www.bad-crook-site.example.comには、次の例のような HTML フォームが含まれています。<h1>Congratulations! You're a Winner!</h1> <form action="https://www.good-banking-site.example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click to collect your prize!" /> </form>悪意のあるサイトではなく、脆弱なサイトにフォームの
actionが POST されていることに注意してください。 これが CSRF の "クロスサイト" 部分です。ユーザーが送信ボタンを選びます。 ブラウザーは要求を行い、要求されたドメイン cookie に対する認証
www.good-banking-site.example.comを自動的に含めます。要求はユーザーの認証コンテキストを使用して
www.good-banking-site.example.comサーバー上で実行され、認証されたユーザーが実行を許可されている任意のアクションを実行できます。
ユーザーがボタンを選んでフォームを送信するシナリオに加えて、悪意のあるサイトでは次のこともできます。
- フォームを自動的に送信するスクリプトを実行する。
- フォーム送信を AJAX 要求として送信する。
- CSS を使用してフォームを非表示にする。
これらの代替シナリオでは、最初に悪意のあるサイトにアクセスすること以外に、ユーザーによる操作や入力は必要ありません。
HTTPS を使用しても CSRF 攻撃を防ぐことはできません。 悪意のあるサイトは、安全でない要求を送信するのと同じくらい簡単に、要求 https://www.good-banking-site.example.com/ を送信できます。
一部の攻撃は GET 要求に応答するエンドポイントを対象にしており、その場合、イメージ タグを使用してアクションを実行できます。 この形式の攻撃は、イメージは許可するが JavaScript はブロックするフォーラム サイトでよく使用されます。 GET 要求で状態を変更するアプリ (変数またはリソースが変更される場合) は、悪意のある攻撃に対して脆弱です。 状態を変更する GET 要求は安全ではありません。 ベスト プラクティスは、GET 要求で状態を変更しないことです。
認証に Cookie を使用する Web アプリに対して CSRF 攻撃が発生する可能性があるのは、次の理由からです。
- ブラウザーは、Web アプリによって発行された Cookie を保存します。
- 保存される Cookie には、認証されたユーザーのセッション Cookie が含まれます。
- ブラウザーは、アプリへの要求がブラウザ内でどのように生成されたかに関係なく、要求ごとにドメインに関連付けられたすべての Cookie を Web アプリに送信します。
ただし、CSRF 攻撃は Cookie の悪用に限定されません。 たとえば、基本認証やダイジェスト認証も脆弱です。 ユーザーが基本認証またはダイジェスト認証を使用してサインインすると、セッションが終了するまで、ブラウザーによって資格情報が自動的に送信されます。
このコンテキストでの "セッション" とは、その間ユーザーが認証されている、クライアント側のセッションを指します。 サーバー側のセッションや ASP.NET Coreセッション ミドルウェアとは無関係です。
ユーザーは、予防措置を講じて CSRF の脆弱性を防ぐことができます。
- Web アプリを使い終わったら、それからサインアウトします。
- ブラウザーの cookie を定期的にクリアします。
ただし、CSRF の脆弱性は、根本的に、エンド ユーザーではなく Web アプリに関する問題です。
認証の基礎
Cookie ベースの認証は、一般的な認証形式です。 トークン ベースの認証システムは普及が広がっています (特にシングルページ アプリケーション (SPA) の場合)。
Cookie ベースの認証
ユーザーは、ユーザー名とパスワードを使って認証を行うと、認証と承認に使用できる認証チケットが含まれるトークンを発行されます。 トークンは、クライアントが行うすべての要求で送信される cookie として格納されます。 この cookie の生成と検証は、 cookie 認証ミドルウェアによって実行されます。 ミドルウェアによって、ユーザー プリンシパルは暗号化された cookie にシリアル化されます。 後続の要求では、ミドルウェアによって cookie が検証され、プリンシパルが再作成されて、そのプリンシパルが HttpContext.User プロパティに割り当てられます。
トークンベースの認証
認証されたユーザーには、トークンが発行されます (偽造防止トークンではありません)。 このトークンには、クレームまたはアプリで保持されているユーザー状態をアプリに示す参照トークンの形式で、ユーザー情報が含まれます。 ユーザーが認証を必要とするリソースにアクセスしようとすると、ベアラー トークンの形式の追加承認ヘッダーと共にトークンがアプリに送信されます。 この方法により、アプリはステートレスになります。 後続の要求ごとに、サーバー側の検証のために要求でトークンが渡されます。 このトークンは、"暗号化" されておらず、"エンコード" されています。 サーバーでは、トークンをデコードして情報にアクセスします。 後続の要求でトークンを送信するため、トークンはブラウザーのローカル ストレージに格納されます。 トークンをブラウザーのローカル ストレージに配置して取得し、ベアラー トークンとして使用すると、CSRF 攻撃に対する保護が提供されます。 ただし、XSS または侵害された外部 JavaScript ファイルを介したスクリプト インジェクションに対してアプリが脆弱な場合、攻撃者はローカル ストレージから任意の値を取得して自分自身に送信する可能性があります。 ASP.NET Core では、既定ですべてのサーバー側出力が変数からエンコードされるため、XSS のリスクが軽減されます。 Html.Raw を使用するか、信頼されていない入力でカスタム コードを使用してこの動作をオーバーライドすると、XSS のリスクが高くなる可能性があります。
トークンがブラウザーのローカル ストレージに格納される場合は、CSRF の脆弱性について心配しないでください。 CSRF が問題になるのは、トークンが cookie に格納さるときです。 詳細ついては、GitHub のイシュー「SPA code sample adds two cookies (SPA のコード サンプルで 2 つの cookie が追加される」をご覧ください。
1 つのドメインでホストされている複数のアプリ
共有ホスティング環境は、セッション ハイジャック、ログイン CSRF、その他の攻撃に対して脆弱です。
example1.contoso.net と example2.contoso.net は異なるホストですが、*.contoso.net ドメインの下のホスト間には暗黙的な信頼関係があります。 この暗黙的な信頼関係により、信頼できないホストが互いの cookie に影響を与えることができる可能性があります (AJAX 要求を管理する同一オリジン ポリシーは、必ずしも HTTP cookie に適用されるとは限りません)。
同じドメインでホストされているアプリ間で信頼された cookie を悪用する攻撃は、ドメインを共有しないことで回避できます。 各アプリが独自のドメインでホストされている場合、悪用するための暗黙的な cookie の信頼関係はありません。
ASP.NET Core での偽造防止
警告
ASP.NET Core では、ASP.NET Core データ保護を使用して偽造防止が実装されています。 サーバー ファームで動作するように、データ保護スタックを構成する必要があります。 詳しくは、データ保護の構成に関する記事をご覧ください。
次のいずれかの API が
FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。 Razor ファイルの次のマークアップによって、偽造防止トークンが自動的に生成されます。
<form method="post">
<!-- ... -->
</form>
同様に、フォームのメソッドが GET でない場合は、IHtmlHelper.BeginForm によって既定で偽造防止トークンが生成されます。
HTML フォーム要素に対する偽造防止トークンの自動生成は、<form> タグに method="post" 属性が含まれていて、次のいずれかが当てはまる場合に行われます。
- action 属性が空です (
action="")。 - action 属性が指定されていません (
<form method="post">)。
HTML フォーム要素に対する偽造防止トークンの自動生成は、無効にすることができます。
偽造防止トークンを明示的に無効にするには、
asp-antiforgery属性を使います。<form method="post" asp-antiforgery="false"> <!-- ... --> </form>タグ ヘルパーの ! オプトアウト シンボルを使うと、フォーム要素がタグ ヘルパーからオプトアウトされます。
<!form method="post"> <!-- ... --> </!form>ビューから
FormTagHelperを削除します。FormTagHelperビューに次のディレクティブを追加することで、ビューから Razor を削除できます。@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages は XSRF/CSRF から自動的に保護されます。 詳細しくは、「XSRF/CSRF および Razor ページ」をご覧ください。
CSRF 攻撃を防ぐ最も一般的な方法は、"シンクロナイザー トークン パターン" (STP) を使うことです。 STP は、ユーザーがフォーム データを含むページを要求するときに使われます。
- サーバーは、現在のユーザーの ID に関連付けられているトークンをクライアントに送信します。
- クライアントは、検証のためにトークンをサーバーに送り返します。
- 認証されたユーザーの ID と一致しないトークンをサーバーが受信した場合、要求は拒否されます。
トークンは一意で、予測できません。 トークンは、一連の要求の適切な順序を保証するためにも使用できます (たとえば、ページ 1 > ページ 2 > ページ 3 という要求の順序を保証します)。 ASP.NET Core MVC と Razor Pages のテンプレートのすべてのフォームで、偽造防止トークンが生成されます。 次の 2 つのビューの例では、偽造防止トークンが生成されます。
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
タグ ヘルパーと HTML ヘルパー <form> を使用せず、偽造防止トークンを @Html.AntiForgeryToken 要素に明示的に追加します。
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
いずれの場合も、ASP.NET Core によって次の例のような非表示のフォーム フィールドが追加されます。
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core には、偽造防止トークンを使用するための 3 つのフィルターが含まれています。
AddControllers での偽造防止
AddControllers を呼び出しても、偽造防止トークンは有効に "なりません"。 組み込みの偽造防止トークンをサポートするには AddControllersWithViews を呼び出す必要があります。
複数のブラウザー タブとシンクロナイザートークンパターン
Synchronizer Token Pattern を使う場合、最後に読み込まれたページにのみ、有効な偽造防止トークンが含まれています。 複数のタブを使うと、問題が発生することがあります。 たとえば、ユーザーが複数のタブを開いた場合:
- 最後に読み込まれたタブにのみ、有効な偽造防止トークンが含まれています。
- 以前に読み込まれたタブからの要求は、次のエラーで失敗します。
Antiforgery token validation failed. The antiforgery cookie token and request token do not match
これで問題が発生する場合は、別の CSRF 保護パターンを検討してください。
AntiforgeryOptions を使用して偽造防止を構成する
アプリのAntiforgeryOptions ファイル内のProgramをカスタマイズします。
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
次の表に示すように、cookie クラスのプロパティを使って偽造防止の CookieBuilder プロパティを設定します。
| 回答内容 | 説明 |
|---|---|
| Cookie | 偽造防止 cookie の作成に使用される設定を決定します。 |
| FormFieldName | ビューで偽造防止トークンをレンダリングするために偽造防止システムによって使用される非表示フォーム フィールドの名前。 |
| HeaderName | 偽造防止システムによって使用されるヘッダーの名前。
null の場合、システムではフォーム データのみが考慮されます。 |
| SuppressXFrameOptionsHeader |
X-Frame-Options ヘッダーの生成を抑制するかどうかを指定します。 既定では、ヘッダーは値 "SAMEORIGIN" を使用して生成されます。 既定値は false です。 |
一部のブラウザーでは、セキュリティで保護されていないエンドポイントが "セキュリティで保護された" フラグを使用して Cookie を設定したり、'secure' フラグが設定されている Cookie を上書きしたりすることはできません (詳細については、「セキュリティで 保護されていない配信元からの 'secure' Cookie の変更の非推奨」を参照してください)。 セキュリティで保護されたエンドポイントと安全でないエンドポイントを混在させるのはアプリの一般的なシナリオであるため、ASP.NET Core では、cookieのcookieをSecurePolicyに設定することで、偽造防止CookieSecurePolicy.Noneなどの一部の Cookie に対するセキュリティで保護されたポリシーの制限を緩和します。 悪意のあるユーザーが偽造防止 cookieを盗んだ場合でも、通常はフォーム フィールド (より一般的) または別の要求ヘッダー (あまり一般的ではない) と認証 cookieを介して送信される偽造防止トークンも盗む必要があります。 認証または承認に関連する Cookie は、 CookieSecurePolicy.Noneよりも強力なポリシーを使用します。
必要に応じて、アプリのcookie ファイルで次のDevelopmentプロパティ設定を使用して、HTTPS 経由でのみ Secure Sockets Layer (SSL) を使用して、AntiforgeryOptions.Cookie以外の環境で偽造防止Programをセキュリティで保護できます。
if (!builder.Environment.IsDevelopment())
{
builder.Services.AddAntiforgery(o =>
{
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
}
詳細については、「CookieAuthenticationOptions」を参照してください。
IAntiforgery を使用して偽造防止トークンを生成する
IAntiforgery では、偽造防止機能を構成するための API が提供されます。
IAntiforgery は Program.cs で WebApplication.Services を使用してリクエストできます。 次の例では、アプリのホーム ページのミドルウェアを使用して偽造防止トークンを生成し、応答で cookieとして送信します。
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
前の例では、cookie という名前の XSRF-TOKEN を設定しています。 クライアントはこの cookie を読み取り、その値を AJAX 要求にアタッチされたヘッダーとして提供できます。 たとえば、Angular には、既定で
偽造防止の検証を要求する
ValidateAntiForgeryToken アクション フィルターは個々のアクションやコントローラーに対して、またはグローバルに適用できます。 このフィルターが適用されているアクションに対して行われた要求は、要求に有効な偽造防止トークンが含まれていない限り、ブロックされます。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
ValidateAntiForgeryToken 属性には、HTTP GET 要求を含む、マークするアクション メソッドへの要求に対するトークンが必要です。
ValidateAntiForgeryToken 属性がアプリのコントローラー全体に適用されている場合は、IgnoreAntiforgeryToken 属性でオーバーライドできます。
安全でない HTTP メソッドについてのみ偽造防止トークンを自動的に検証する
ValidateAntiForgeryToken 属性を広範に適用してから IgnoreAntiforgeryToken 属性でオーバーライドするのではなく、AutoValidateAntiforgeryToken 属性を使用できます。 この属性は、ValidateAntiForgeryToken 属性と同じように動作しますが、次の HTTP メソッドを使用して行われた要求についてはトークンを要求しない点が異なります。
- GET
- HEAD
- オプション
- TRACE
非 API のシナリオでは、AutoValidateAntiforgeryToken を広範に使うことをお勧めします。 この属性により、POST アクションが既定で保護されるようになります。 代わりの方法は、ValidateAntiForgeryToken が個々のアクション メソッドに適用されない限り、既定で偽造防止トークンを無視することです。 このシナリオでは、POST アクション メソッドが誤って保護されないままになり、アプリが CSRF 攻撃に対して脆弱になる可能性が高くなります。 すべての POST で、偽造防止トークンを送信する必要があります。
API には、トークンの cookie 以外の部分を送信するための自動メカニズムはありません。 実装は、クライアント コードの実装によって異なる場合があります。 いくつかの例を次に示します。
クラスレベルの例:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
グローバルな例:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
グローバルまたはコントローラーの偽造防止属性をオーバーライドする
IgnoreAntiforgeryToken フィルターは、特定のアクション (またはコントローラー) に対して偽造防止トークンを不要にするために使用されます。 このフィルターを適用すると、より高いレベル (グローバルまたはコントローラー上) で指定されている ValidateAntiForgeryToken および AutoValidateAntiforgeryToken フィルターがオーバーライドされます。
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
認証後にトークンを更新する
ユーザーが認証された後、ユーザーをビューまたは Razor Pages ページにリダイレクトすることによって、トークンを更新する必要があります。
JavaScript、AJAX、SPA
従来の HTML ベースのアプリでは、偽造防止トークンは非表示のフォーム フィールドを使用してサーバーに渡されます。 最新の JavaScript ベースのアプリと SPA では、多くの要求がプログラムによって行われます。 これらの AJAX 要求では、要求ヘッダーや cookie などの他の手法を使ってトークンを送信できます。
cookie が、認証トークンの格納と、サーバーでの API 要求の認証に使用されている場合、CSRF が問題になる可能性があります。 ローカル ストレージがトークンの格納に使われている場合は、ローカル ストレージの値はすべての要求でサーバーに自動的に送信されないため、CSRF の脆弱性が軽減される可能性があります。 ローカル ストレージを使用してクライアントに偽造防止トークンを格納し、要求ヘッダーとしてトークンを送信することをお勧めします。
JavaScript
JavaScript とビューを使うと、ビュー内からサービスを使ってトークンを作成できます。 ビューに IAntiforgery サービスを挿入し、GetAndStoreTokens を呼び出します。
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
前の例では、JavaScript を使用して、AJAX POST ヘッダーの非表示フィールドの値を読み取っています。
この方法を使用すると、サーバーからの cookie の設定やクライアントからの読み取りを、直接処理する必要がなくなります ただし、IAntiforgery サービスを挿入できない場合は、JavaScript を使用して cookie のトークンにアクセスします。
- サーバーへの追加要求でトークンにアクセスします (通常は
same-origin)。 - cookie のコンテンツを使用して、トークンの値を含むヘッダーを作成します。
スクリプトで、X-XSRF-TOKEN という名前の要求ヘッダーでトークンを送信することが想定されている場合は、X-XSRF-TOKEN ヘッダーを検索するように偽造防止サービスを構成します。
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
次の例では、要求トークンを書き込む保護されたエンドポイントを、JavaScript で読み取り可能な cookie に追加します。
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
次の例では、JavaScript を使用して AJAX 要求を行い、トークンを取得して、適切なヘッダーを使って別の要求を行います。
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
Note
要求ヘッダーとフォーム ペイロードの両方で偽造防止トークンが提供される場合、ヘッダー内のトークンのみが検証されます。
Minimal API を使用した偽造防止
Minimal APIs では含まれているフィルター (ValidateAntiForgeryToken、AutoValidateAntiforgeryToken、IgnoreAntiforgeryToken) の使用がサポートされていませんが、IAntiforgery により、要求を検証するために必要な API が提供されます。
次の例では、偽造防止トークンを検証するフィルターを作成します。
internal static class AntiForgeryExtensions
{
public static TBuilder ValidateAntiforgery<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
{
return builder.AddEndpointFilter(routeHandlerFilter: async (context, next) =>
{
try
{
var antiForgeryService = context.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
await antiForgeryService.ValidateRequestAsync(context.HttpContext);
}
catch (AntiforgeryValidationException)
{
return Results.BadRequest("Antiforgery token validation failed.");
}
return await next(context);
});
}
}
その後、そのフィルターをエンドポイントに適用できます。
app.MapPost("api/upload", (IFormFile name) => Results.Accepted())
.RequireAuthorization()
.ValidateAntiforgery();
Windows 認証と偽造防止 cookie
Windows 認証を使用するときは、cookie の場合と同じ方法で、アプリケーション エンドポイントを CSRF 攻撃から保護する必要があります。 ブラウザーによって認証コンテキストがサーバーに暗黙的に送信されるため、エンドポイントを CSRF 攻撃から保護する必要があります。
偽造防止を強化する
IAntiforgeryAdditionalDataProvider 型を使用すると、開発者は、トークンごとに追加データをラウンドトリップすることで、CSRF 対策システムの動作を強化できます。 フィールド トークンを生成するたびに GetAdditionalData メソッドを呼び出し、戻り値を生成されるトークンに埋め込みます。 実装者は、タイムスタンプ、nonce、または他の値を返し、トークンの検証時に ValidateAdditionalData を呼び出してこのデータを検証できます。 クライアントのユーザー名は生成されたトークンに既に埋め込まれているので、この情報を含める必要はありません。 トークンに補足データが含まれていても、IAntiForgeryAdditionalDataProvider が構成されていない場合は、補足データは検証されません。
追加のリソース
クロスサイト リクエスト フォージェリ (XSRF または CSRF とも呼ばれます) は、Web ホスト アプリに対する攻撃であり、悪意のある Web アプリがクライアント ブラウザーとそのブラウザーを信頼する Web アプリとの間の対話に、影響を与える可能性があります。 これらの攻撃は、Web ブラウザーが Web サイトへのすべての要求に対して何らかの種類の認証トークンを自動的に送信するために発生する可能性があります。 この形式の悪用は、攻撃でユーザーの以前に認証されたセッションが利用されるため、"ワンクリック攻撃" または "セッション ライディング" としても知られます。
CSRF 攻撃の例:
ユーザーが、フォーム認証を使用して
www.good-banking-site.example.comにサインインします。 サーバーはユーザーを認証し、認証 cookie を含む応答を発行します。 サイトは、有効な認証 cookie と共に受信したすべての要求を信頼するため、攻撃に対して脆弱です。ユーザーが悪意のあるサイト
www.bad-crook-site.example.comにアクセスします。悪意のあるサイト
www.bad-crook-site.example.comには、次の例のような HTML フォームが含まれています。<h1>Congratulations! You're a Winner!</h1> <form action="https://www.good-banking-site.example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click to collect your prize!" /> </form>悪意のあるサイトではなく、脆弱なサイトにフォームの
actionが POST されていることに注意してください。 これが CSRF の "クロスサイト" 部分です。ユーザーが送信ボタンを選びます。 ブラウザーは要求を行い、要求されたドメイン cookie に対する認証
www.good-banking-site.example.comを自動的に含めます。要求はユーザーの認証コンテキストを使用して
www.good-banking-site.example.comサーバー上で実行され、認証されたユーザーが実行を許可されている任意のアクションを実行できます。
ユーザーがボタンを選んでフォームを送信するシナリオに加えて、悪意のあるサイトでは次のこともできます。
- フォームを自動的に送信するスクリプトを実行する。
- フォーム送信を AJAX 要求として送信する。
- CSS を使用してフォームを非表示にする。
これらの代替シナリオでは、最初に悪意のあるサイトにアクセスすること以外に、ユーザーによる操作や入力は必要ありません。
HTTPS を使用しても CSRF 攻撃を防ぐことはできません。 悪意のあるサイトは、安全でない要求を送信するのと同じくらい簡単に、要求 https://www.good-banking-site.example.com/ を送信できます。
一部の攻撃は GET 要求に応答するエンドポイントを対象にしており、その場合、イメージ タグを使用してアクションを実行できます。 この形式の攻撃は、イメージは許可するが JavaScript はブロックするフォーラム サイトでよく使用されます。 GET 要求で状態を変更するアプリ (変数またはリソースが変更される場合) は、悪意のある攻撃に対して脆弱です。 状態を変更する GET 要求は安全ではありません。 ベスト プラクティスは、GET 要求で状態を変更しないことです。
認証に Cookie を使用する Web アプリに対して CSRF 攻撃が発生する可能性があるのは、次の理由からです。
- ブラウザーは、Web アプリによって発行された Cookie を保存します。
- 保存される Cookie には、認証されたユーザーのセッション Cookie が含まれます。
- ブラウザーは、アプリへの要求がブラウザ内でどのように生成されたかに関係なく、要求ごとにドメインに関連付けられたすべての Cookie を Web アプリに送信します。
ただし、CSRF 攻撃は Cookie の悪用に限定されません。 たとえば、基本認証やダイジェスト認証も脆弱です。 ユーザーが基本認証またはダイジェスト認証を使用してサインインすると、セッションが終了するまで、ブラウザーによって資格情報が自動的に送信されます。
このコンテキストでの "セッション" とは、その間ユーザーが認証されている、クライアント側のセッションを指します。 サーバー側のセッションや ASP.NET Coreセッション ミドルウェアとは無関係です。
ユーザーは、予防措置を講じて CSRF の脆弱性を防ぐことができます。
- Web アプリを使い終わったら、それからサインアウトします。
- ブラウザーの cookie を定期的にクリアします。
ただし、CSRF の脆弱性は、根本的に、エンド ユーザーではなく Web アプリに関する問題です。
認証の基礎
Cookie ベースの認証は、一般的な認証形式です。 トークン ベースの認証システムは普及が広がっています (特にシングルページ アプリケーション (SPA) の場合)。
Cookie ベースの認証
ユーザーは、ユーザー名とパスワードを使って認証を行うと、認証と承認に使用できる認証チケットが含まれるトークンを発行されます。 トークンは、クライアントが行うすべての要求で送信される cookie として格納されます。 この cookie の生成と検証は、 cookie 認証ミドルウェアによって実行されます。 ミドルウェアによって、ユーザー プリンシパルは暗号化された cookie にシリアル化されます。 後続の要求では、ミドルウェアによって cookie が検証され、プリンシパルが再作成されて、そのプリンシパルが HttpContext.User プロパティに割り当てられます。
トークンベースの認証
認証されたユーザーには、トークンが発行されます (偽造防止トークンではありません)。 このトークンには、クレームまたはアプリで保持されているユーザー状態をアプリに示す参照トークンの形式で、ユーザー情報が含まれます。 ユーザーが認証を必要とするリソースにアクセスしようとすると、ベアラー トークンの形式の追加承認ヘッダーと共にトークンがアプリに送信されます。 この方法により、アプリはステートレスになります。 後続の要求ごとに、サーバー側の検証のために要求でトークンが渡されます。 このトークンは、"暗号化" されておらず、"エンコード" されています。 サーバーでは、トークンをデコードして情報にアクセスします。 後続の要求でトークンを送信するため、トークンはブラウザーのローカル ストレージに格納されます。 トークンがブラウザーのローカル ストレージに格納される場合は、CSRF の脆弱性について心配しないでください。 CSRF が問題になるのは、トークンが cookie に格納さるときです。 詳細ついては、GitHub のイシュー「SPA code sample adds two cookies (SPA のコード サンプルで 2 つの cookie が追加される」をご覧ください。
1 つのドメインでホストされている複数のアプリ
共有ホスティング環境は、セッション ハイジャック、ログイン CSRF、その他の攻撃に対して脆弱です。
example1.contoso.net と example2.contoso.net は異なるホストですが、*.contoso.net ドメインの下のホスト間には暗黙的な信頼関係があります。 この暗黙的な信頼関係により、信頼できないホストが互いの cookie に影響を与えることができる可能性があります (AJAX 要求を管理する同一オリジン ポリシーは、必ずしも HTTP cookie に適用されるとは限りません)。
同じドメインでホストされているアプリ間で信頼された cookie を悪用する攻撃は、ドメインを共有しないことで回避できます。 各アプリが独自のドメインでホストされている場合、悪用するための暗黙的な cookie の信頼関係はありません。
ASP.NET Core での偽造防止
警告
ASP.NET Core では、ASP.NET Core データ保護を使用して偽造防止が実装されています。 サーバー ファームで動作するように、データ保護スタックを構成する必要があります。 詳しくは、データ保護の構成に関する記事をご覧ください。
次のいずれかの API が
FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。 Razor ファイルの次のマークアップによって、偽造防止トークンが自動的に生成されます。
<form method="post">
<!-- ... -->
</form>
同様に、フォームのメソッドが GET でない場合は、IHtmlHelper.BeginForm によって既定で偽造防止トークンが生成されます。
HTML フォーム要素に対する偽造防止トークンの自動生成は、<form> タグに method="post" 属性が含まれていて、次のいずれかが当てはまる場合に行われます。
- action 属性が空です (
action="")。 - action 属性が指定されていません (
<form method="post">)。
HTML フォーム要素に対する偽造防止トークンの自動生成は、無効にすることができます。
偽造防止トークンを明示的に無効にするには、
asp-antiforgery属性を使います。<form method="post" asp-antiforgery="false"> <!-- ... --> </form>タグ ヘルパーの ! オプトアウト シンボルを使うと、フォーム要素がタグ ヘルパーからオプトアウトされます。
<!form method="post"> <!-- ... --> </!form>ビューから
FormTagHelperを削除します。FormTagHelperビューに次のディレクティブを追加することで、ビューから Razor を削除できます。@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages は XSRF/CSRF から自動的に保護されます。 詳細しくは、「XSRF/CSRF および Razor ページ」をご覧ください。
CSRF 攻撃を防ぐ最も一般的な方法は、"シンクロナイザー トークン パターン" (STP) を使うことです。 STP は、ユーザーがフォーム データを含むページを要求するときに使われます。
- サーバーは、現在のユーザーの ID に関連付けられているトークンをクライアントに送信します。
- クライアントは、検証のためにトークンをサーバーに送り返します。
- 認証されたユーザーの ID と一致しないトークンをサーバーが受信した場合、要求は拒否されます。
トークンは一意で、予測できません。 トークンは、一連の要求の適切な順序を保証するためにも使用できます (たとえば、ページ 1 > ページ 2 > ページ 3 という要求の順序を保証します)。 ASP.NET Core MVC と Razor Pages のテンプレートのすべてのフォームで、偽造防止トークンが生成されます。 次の 2 つのビューの例では、偽造防止トークンが生成されます。
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
タグ ヘルパーと HTML ヘルパー <form> を使用せず、偽造防止トークンを @Html.AntiForgeryToken 要素に明示的に追加します。
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
いずれの場合も、ASP.NET Core によって次の例のような非表示のフォーム フィールドが追加されます。
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core には、偽造防止トークンを使用するための 3 つのフィルターが含まれています。
AddControllers での偽造防止
AddControllers を呼び出しても、偽造防止トークンは有効に "なりません"。 組み込みの偽造防止トークンをサポートするには AddControllersWithViews を呼び出す必要があります。
複数のブラウザー タブとシンクロナイザートークンパターン
Synchronizer Token Pattern を使う場合、最後に読み込まれたページにのみ、有効な偽造防止トークンが含まれています。 複数のタブを使うと、問題が発生することがあります。 たとえば、ユーザーが複数のタブを開いた場合:
- 最後に読み込まれたタブにのみ、有効な偽造防止トークンが含まれています。
- 以前に読み込まれたタブからの要求は、次のエラーで失敗します。
Antiforgery token validation failed. The antiforgery cookie token and request token do not match
これで問題が発生する場合は、別の CSRF 保護パターンを検討してください。
AntiforgeryOptions を使用して偽造防止を構成する
アプリのAntiforgeryOptions ファイル内のProgramをカスタマイズします。
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
次の表に示すように、cookie クラスのプロパティを使って偽造防止の CookieBuilder プロパティを設定します。
| 回答内容 | 説明 |
|---|---|
| Cookie | 偽造防止 cookie の作成に使用される設定を決定します。 |
| FormFieldName | ビューで偽造防止トークンをレンダリングするために偽造防止システムによって使用される非表示フォーム フィールドの名前。 |
| HeaderName | 偽造防止システムによって使用されるヘッダーの名前。
null の場合、システムではフォーム データのみが考慮されます。 |
| SuppressXFrameOptionsHeader |
X-Frame-Options ヘッダーの生成を抑制するかどうかを指定します。 既定では、ヘッダーは値 "SAMEORIGIN" を使用して生成されます。 既定値は false です。 |
一部のブラウザーでは、セキュリティで保護されていないエンドポイントが "セキュリティで保護された" フラグを使用して Cookie を設定したり、'secure' フラグが設定されている Cookie を上書きしたりすることはできません (詳細については、「セキュリティで 保護されていない配信元からの 'secure' Cookie の変更の非推奨」を参照してください)。 セキュリティで保護されたエンドポイントと安全でないエンドポイントを混在させるのはアプリの一般的なシナリオであるため、ASP.NET Core では、cookieのcookieをSecurePolicyに設定することで、偽造防止CookieSecurePolicy.Noneなどの一部の Cookie に対するセキュリティで保護されたポリシーの制限を緩和します。 悪意のあるユーザーが偽造防止 cookieを盗んだ場合でも、通常はフォーム フィールド (より一般的) または別の要求ヘッダー (あまり一般的ではない) と認証 cookieを介して送信される偽造防止トークンも盗む必要があります。 認証または承認に関連する Cookie は、 CookieSecurePolicy.Noneよりも強力なポリシーを使用します。
必要に応じて、アプリのcookie ファイルで次のDevelopmentプロパティ設定を使用して、HTTPS 経由でのみ Secure Sockets Layer (SSL) を使用して、AntiforgeryOptions.Cookie以外の環境で偽造防止Programをセキュリティで保護できます。
if (!builder.Environment.IsDevelopment())
{
builder.Services.AddAntiforgery(o =>
{
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
}
詳細については、「CookieAuthenticationOptions」を参照してください。
IAntiforgery を使用して偽造防止トークンを生成する
IAntiforgery では、偽造防止機能を構成するための API が提供されます。
IAntiforgery は Program.cs で WebApplication.Services を使用してリクエストできます。 次の例では、アプリのホーム ページのミドルウェアを使用して偽造防止トークンを生成し、応答で cookieとして送信します。
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
前の例では、cookie という名前の XSRF-TOKEN を設定しています。 クライアントはこの cookie を読み取り、その値を AJAX 要求にアタッチされたヘッダーとして提供できます。 たとえば、Angular には、既定で
偽造防止の検証を要求する
ValidateAntiForgeryToken アクション フィルターは個々のアクションやコントローラーに対して、またはグローバルに適用できます。 このフィルターが適用されているアクションに対して行われた要求は、要求に有効な偽造防止トークンが含まれていない限り、ブロックされます。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
ValidateAntiForgeryToken 属性には、HTTP GET 要求を含む、マークするアクション メソッドへの要求に対するトークンが必要です。
ValidateAntiForgeryToken 属性がアプリのコントローラー全体に適用されている場合は、IgnoreAntiforgeryToken 属性でオーバーライドできます。
安全でない HTTP メソッドについてのみ偽造防止トークンを自動的に検証する
ValidateAntiForgeryToken 属性を広範に適用してから IgnoreAntiforgeryToken 属性でオーバーライドするのではなく、AutoValidateAntiforgeryToken 属性を使用できます。 この属性は、ValidateAntiForgeryToken 属性と同じように動作しますが、次の HTTP メソッドを使用して行われた要求についてはトークンを要求しない点が異なります。
- GET
- HEAD
- オプション
- TRACE
非 API のシナリオでは、AutoValidateAntiforgeryToken を広範に使うことをお勧めします。 この属性により、POST アクションが既定で保護されるようになります。 代わりの方法は、ValidateAntiForgeryToken が個々のアクション メソッドに適用されない限り、既定で偽造防止トークンを無視することです。 このシナリオでは、POST アクション メソッドが誤って保護されないままになり、アプリが CSRF 攻撃に対して脆弱になる可能性が高くなります。 すべての POST で、偽造防止トークンを送信する必要があります。
API には、トークンの cookie 以外の部分を送信するための自動メカニズムはありません。 実装は、クライアント コードの実装によって異なる場合があります。 いくつかの例を次に示します。
クラスレベルの例:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
グローバルな例:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
グローバルまたはコントローラーの偽造防止属性をオーバーライドする
IgnoreAntiforgeryToken フィルターは、特定のアクション (またはコントローラー) に対して偽造防止トークンを不要にするために使用されます。 このフィルターを適用すると、より高いレベル (グローバルまたはコントローラー上) で指定されている ValidateAntiForgeryToken および AutoValidateAntiforgeryToken フィルターがオーバーライドされます。
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
認証後にトークンを更新する
ユーザーが認証された後、ユーザーをビューまたは Razor Pages ページにリダイレクトすることによって、トークンを更新する必要があります。
JavaScript、AJAX、SPA
従来の HTML ベースのアプリでは、偽造防止トークンは非表示のフォーム フィールドを使用してサーバーに渡されます。 最新の JavaScript ベースのアプリと SPA では、多くの要求がプログラムによって行われます。 これらの AJAX 要求では、要求ヘッダーや cookie などの他の手法を使ってトークンを送信できます。
cookie が、認証トークンの格納と、サーバーでの API 要求の認証に使用されている場合、CSRF が問題になる可能性があります。 ローカル ストレージがトークンの格納に使われている場合は、ローカル ストレージの値はすべての要求でサーバーに自動的に送信されないため、CSRF の脆弱性が軽減される可能性があります。 ローカル ストレージを使用してクライアントに偽造防止トークンを格納し、要求ヘッダーとしてトークンを送信することをお勧めします。
JavaScript
JavaScript とビューを使うと、ビュー内からサービスを使ってトークンを作成できます。 ビューに IAntiforgery サービスを挿入し、GetAndStoreTokens を呼び出します。
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
前の例では、JavaScript を使用して、AJAX POST ヘッダーの非表示フィールドの値を読み取っています。
この方法を使用すると、サーバーからの cookie の設定やクライアントからの読み取りを、直接処理する必要がなくなります ただし、IAntiforgery サービスを挿入できない場合、JavaScript はサーバーへの追加リクエスト (通常は same-origin) から取得された Cookie 内のトークンにアクセスし、cookie の内容を使用してトークンの値を含むヘッダーを作成することもできます。
スクリプトで、X-XSRF-TOKEN という名前の要求ヘッダーでトークンを送信することが想定されている場合は、X-XSRF-TOKEN ヘッダーを検索するように偽造防止サービスを構成します。
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
次の例では、要求トークンを書き込む保護されたエンドポイントを、JavaScript で読み取り可能な cookie に追加します。
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
次の例では、JavaScript を使用して AJAX 要求を行い、トークンを取得して、適切なヘッダーを使って別の要求を行います。
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
Windows 認証と偽造防止 cookie
Windows 認証を使用するときは、cookie の場合と同じ方法で、アプリケーション エンドポイントを CSRF 攻撃から保護する必要があります。 ブラウザーによって認証コンテキストがサーバーに暗黙的に送信されるため、エンドポイントを CSRF 攻撃から保護する必要があります。
偽造防止を強化する
IAntiforgeryAdditionalDataProvider 型を使用すると、開発者は、トークンごとに追加データをラウンドトリップすることで、CSRF 対策システムの動作を強化できます。 フィールド トークンを生成するたびに GetAdditionalData メソッドを呼び出し、戻り値を生成されるトークンに埋め込みます。 実装者は、タイムスタンプ、nonce、または他の値を返し、トークンの検証時に ValidateAdditionalData を呼び出してこのデータを検証できます。 クライアントのユーザー名は生成されたトークンに既に埋め込まれているので、この情報を含める必要はありません。 トークンに補足データが含まれていても、IAntiForgeryAdditionalDataProvider が構成されていない場合は、補足データは検証されません。
追加のリソース
クロスサイト リクエスト フォージェリ (XSRF または CSRF とも呼ばれます) は、Web ホスト アプリに対する攻撃であり、悪意のある Web アプリがクライアント ブラウザーとそのブラウザーを信頼する Web アプリとの間の対話に、影響を与える可能性があります。 これらの攻撃は、Web ブラウザーが Web サイトへのすべての要求に対して何らかの種類の認証トークンを自動的に送信するために発生する可能性があります。 この形式の悪用は、攻撃でユーザーの以前に認証されたセッションが利用されるため、"ワンクリック攻撃" または "セッション ライディング" としても知られます。
CSRF 攻撃の例:
ユーザーが、フォーム認証を使用して
www.good-banking-site.example.comにサインインします。 サーバーはユーザーを認証し、認証 cookie を含む応答を発行します。 サイトは、有効な認証 cookie と共に受信したすべての要求を信頼するため、攻撃に対して脆弱です。ユーザーが悪意のあるサイト
www.bad-crook-site.example.comにアクセスします。悪意のあるサイト
www.bad-crook-site.example.comには、次の例のような HTML フォームが含まれています。<h1>Congratulations! You're a Winner!</h1> <form action="https://www.good-banking-site.example.com/api/account" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="submit" value="Click to collect your prize!" /> </form>悪意のあるサイトではなく、脆弱なサイトにフォームの
actionが POST されていることに注意してください。 これが CSRF の "クロスサイト" 部分です。ユーザーが送信ボタンを選びます。 ブラウザーは要求を行い、要求されたドメイン cookie に対する認証
www.good-banking-site.example.comを自動的に含めます。要求はユーザーの認証コンテキストを使用して
www.good-banking-site.example.comサーバー上で実行され、認証されたユーザーが実行を許可されている任意のアクションを実行できます。
ユーザーがボタンを選んでフォームを送信するシナリオに加えて、悪意のあるサイトでは次のこともできます。
- フォームを自動的に送信するスクリプトを実行する。
- フォーム送信を AJAX 要求として送信する。
- CSS を使用してフォームを非表示にする。
これらの代替シナリオでは、最初に悪意のあるサイトにアクセスすること以外に、ユーザーによる操作や入力は必要ありません。
HTTPS を使用しても CSRF 攻撃を防ぐことはできません。 悪意のあるサイトは、安全でない要求を送信するのと同じくらい簡単に、要求 https://www.good-banking-site.example.com/ を送信できます。
一部の攻撃は GET 要求に応答するエンドポイントを対象にしており、その場合、イメージ タグを使用してアクションを実行できます。 この形式の攻撃は、イメージは許可するが JavaScript はブロックするフォーラム サイトでよく使用されます。 GET 要求で状態を変更するアプリ (変数またはリソースが変更される場合) は、悪意のある攻撃に対して脆弱です。 状態を変更する GET 要求は安全ではありません。 ベスト プラクティスは、GET 要求で状態を変更しないことです。
認証に Cookie を使用する Web アプリに対して CSRF 攻撃が発生する可能性があるのは、次の理由からです。
- ブラウザーは、Web アプリによって発行された Cookie を保存します。
- 保存される Cookie には、認証されたユーザーのセッション Cookie が含まれます。
- ブラウザーは、アプリへの要求がブラウザ内でどのように生成されたかに関係なく、要求ごとにドメインに関連付けられたすべての Cookie を Web アプリに送信します。
ただし、CSRF 攻撃は Cookie の悪用に限定されません。 たとえば、基本認証やダイジェスト認証も脆弱です。 ユーザーが基本認証またはダイジェスト認証を使用してサインインすると、セッションが終了するまで、ブラウザーによって資格情報が自動的に送信されます。
このコンテキストでの "セッション" とは、その間ユーザーが認証されている、クライアント側のセッションを指します。 サーバー側のセッションや ASP.NET Coreセッション ミドルウェアとは無関係です。
ユーザーは、予防措置を講じて CSRF の脆弱性を防ぐことができます。
- Web アプリを使い終わったら、それからサインアウトします。
- ブラウザーの cookie を定期的にクリアします。
ただし、CSRF の脆弱性は、根本的に、エンド ユーザーではなく Web アプリに関する問題です。
認証の基礎
Cookie ベースの認証は、一般的な認証形式です。 トークン ベースの認証システムは普及が広がっています (特にシングルページ アプリケーション (SPA) の場合)。
Cookie ベースの認証
ユーザーは、ユーザー名とパスワードを使って認証を行うと、認証と承認に使用できる認証チケットが含まれるトークンを発行されます。 トークンは、クライアントが行うすべての要求で送信される cookie として格納されます。 この cookie の生成と検証は、 cookie 認証ミドルウェアによって実行されます。 ミドルウェアによって、ユーザー プリンシパルは暗号化された cookie にシリアル化されます。 後続の要求では、ミドルウェアによって cookie が検証され、プリンシパルが再作成されて、そのプリンシパルが HttpContext.User プロパティに割り当てられます。
トークンベースの認証
認証されたユーザーには、トークンが発行されます (偽造防止トークンではありません)。 このトークンには、クレームまたはアプリで保持されているユーザー状態をアプリに示す参照トークンの形式で、ユーザー情報が含まれます。 ユーザーが認証を必要とするリソースにアクセスしようとすると、ベアラー トークンの形式の追加承認ヘッダーと共にトークンがアプリに送信されます。 この方法により、アプリはステートレスになります。 後続の要求ごとに、サーバー側の検証のために要求でトークンが渡されます。 このトークンは、"暗号化" されておらず、"エンコード" されています。 サーバーでは、トークンをデコードして情報にアクセスします。 後続の要求でトークンを送信するため、トークンはブラウザーのローカル ストレージに格納されます。 トークンがブラウザーのローカル ストレージに格納される場合は、CSRF の脆弱性について心配しないでください。 CSRF が問題になるのは、トークンが cookie に格納さるときです。 詳細ついては、GitHub のイシュー「SPA code sample adds two cookies (SPA のコード サンプルで 2 つの cookie が追加される」をご覧ください。
1 つのドメインでホストされている複数のアプリ
共有ホスティング環境は、セッション ハイジャック、ログイン CSRF、その他の攻撃に対して脆弱です。
example1.contoso.net と example2.contoso.net は異なるホストですが、*.contoso.net ドメインの下のホスト間には暗黙的な信頼関係があります。 この暗黙的な信頼関係により、信頼できないホストが互いの cookie に影響を与えることができる可能性があります (AJAX 要求を管理する同一オリジン ポリシーは、必ずしも HTTP cookie に適用されるとは限りません)。
同じドメインでホストされているアプリ間で信頼された cookie を悪用する攻撃は、ドメインを共有しないことで回避できます。 各アプリが独自のドメインでホストされている場合、悪用するための暗黙的な cookie の信頼関係はありません。
ASP.NET Core の偽造防止の構成
警告
ASP.NET Core では、ASP.NET Core データ保護を使用して偽造防止が実装されています。 サーバー ファームで動作するように、データ保護スタックを構成する必要があります。 詳しくは、データ保護の構成に関する記事をご覧ください。
次のいずれかの API が
ASP.NET Core 2.0 以降では、FormTagHelper により HTML フォーム要素に偽造防止トークンが挿入されます。 Razor ファイルの次のマークアップによって、偽造防止トークンが自動的に生成されます。
<form method="post">
...
</form>
同様に、フォームのメソッドが GET でない場合は、IHtmlHelper.BeginForm によって既定で偽造防止トークンが生成されます。
HTML フォーム要素に対する偽造防止トークンの自動生成は、<form> タグに method="post" 属性が含まれていて、次のいずれかが当てはまる場合に行われます。
- action 属性が空です (
action="")。 - action 属性が指定されていません (
<form method="post">)。
HTML フォーム要素に対する偽造防止トークンの自動生成は、無効にすることができます。
偽造防止トークンを明示的に無効にするには、
asp-antiforgery属性を使います。<form method="post" asp-antiforgery="false"> ... </form>タグ ヘルパーの ! オプトアウト シンボルを使うと、フォーム要素がタグ ヘルパーからオプトアウトされます。
<!form method="post"> ... </!form>ビューから
FormTagHelperを削除します。FormTagHelperビューに次のディレクティブを追加することで、ビューから Razor を削除できます。@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages は XSRF/CSRF から自動的に保護されます。 詳細しくは、「XSRF/CSRF および Razor ページ」をご覧ください。
CSRF 攻撃を防ぐ最も一般的な方法は、"シンクロナイザー トークン パターン" (STP) を使うことです。 STP は、ユーザーがフォーム データを含むページを要求するときに使われます。
- サーバーは、現在のユーザーの ID に関連付けられているトークンをクライアントに送信します。
- クライアントは、検証のためにトークンをサーバーに送り返します。
- 認証されたユーザーの ID と一致しないトークンをサーバーが受信した場合、要求は拒否されます。
トークンは一意で、予測できません。 トークンは、一連の要求の適切な順序を保証するためにも使用できます (たとえば、ページ 1 > ページ 2 > ページ 3 という要求の順序を保証します)。 ASP.NET Core MVC と Razor Pages のテンプレートのすべてのフォームで、偽造防止トークンが生成されます。 次の 2 つのビューの例では、偽造防止トークンが生成されます。
<form asp-controller="Todo" asp-action="Create" method="post">
...
</form>
@using (Html.BeginForm("Create", "Todo"))
{
...
}
タグ ヘルパーと HTML ヘルパー <form> を使用せず、偽造防止トークンを @Html.AntiForgeryToken 要素に明示的に追加します。
<form action="/" method="post">
@Html.AntiForgeryToken()
</form>
いずれの場合も、ASP.NET Core によって次の例のような非表示のフォーム フィールドが追加されます。
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core には、偽造防止トークンを使用するための 3 つのフィルターが含まれています。
偽造防止オプション
AntiforgeryOptions を Startup.ConfigureServices でカスタマイズします。
services.AddAntiforgery(options =>
{
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
次の表に示すように、cookie クラスのプロパティを使って偽造防止の CookieBuilder プロパティを設定します。
| 回答内容 | 説明 |
|---|---|
| Cookie | 偽造防止 cookie の作成に使用される設定を決定します。 |
| FormFieldName | ビューで偽造防止トークンをレンダリングするために偽造防止システムによって使用される非表示フォーム フィールドの名前。 |
| HeaderName | 偽造防止システムによって使用されるヘッダーの名前。
null の場合、システムではフォーム データのみが考慮されます。 |
| SuppressXFrameOptionsHeader |
X-Frame-Options ヘッダーの生成を抑制するかどうかを指定します。 既定では、ヘッダーは値 "SAMEORIGIN" を使用して生成されます。 既定値は false です。 |
一部のブラウザーでは、セキュリティで保護されていないエンドポイントが "セキュリティで保護された" フラグを使用して Cookie を設定したり、'secure' フラグが設定されている Cookie を上書きしたりすることはできません (詳細については、「セキュリティで 保護されていない配信元からの 'secure' Cookie の変更の非推奨」を参照してください)。 セキュリティで保護されたエンドポイントと安全でないエンドポイントを混在させるのはアプリの一般的なシナリオであるため、ASP.NET Core では、cookieのcookieをSecurePolicyに設定することで、偽造防止CookieSecurePolicy.Noneなどの一部の Cookie に対するセキュリティで保護されたポリシーの制限を緩和します。 悪意のあるユーザーが偽造防止 cookieを盗んだ場合でも、通常はフォーム フィールド (より一般的) または別の要求ヘッダー (あまり一般的ではない) と認証 cookieを介して送信される偽造防止トークンも盗む必要があります。 認証または承認に関連する Cookie は、 CookieSecurePolicy.Noneよりも強力なポリシーを使用します。
必要に応じて、HTTPS 経由でのみ Secure Sockets Layer (SSL) を使用して、非cookie環境で偽造防止Developmentをセキュリティで保護し、アプリのAntiforgeryOptions.Cookie クラスで次のStartupプロパティを設定できます。
public class Startup
{
public Startup(IConfiguration configuration, IHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
// Other services are registered here
if (!Environment.IsDevelopment())
{
services.AddAntiforgery(o =>
{
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Request processing pipeline
}
}
詳細については、「CookieAuthenticationOptions」を参照してください。
IAntiforgery を使用して偽造防止機能を構成する
IAntiforgery では、偽造防止機能を構成するための API が提供されます。
IAntiforgery は、Configure クラスの Startup メソッドで要求できます。
次の例では
- アプリのホーム ページからのミドルウェアは、偽造防止トークンを生成し、cookieとして応答で送信するために使用されます。
- この要求トークンは、cookie のセクションで説明した既定の Angular 名前付け規則を使い、JavaScript で読み取り可能な として送信されます。
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
偽造防止の検証を要求する
ValidateAntiForgeryToken は、個々のアクションやコントローラー、またはグローバルに適用できるアクション フィルターです。 このフィルターが適用されているアクションに対して行われた要求は、要求に有効な偽造防止トークンが含まれていない限り、ブロックされます。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result =
await _userManager.RemoveLoginAsync(
user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}
ValidateAntiForgeryToken 属性には、HTTP GET 要求を含む、マークするアクション メソッドへの要求に対するトークンが必要です。
ValidateAntiForgeryToken 属性がアプリのコントローラー全体に適用されている場合は、IgnoreAntiforgeryToken 属性でオーバーライドできます。
Note
ASP.NET Core では、GET 要求への自動的な偽造防止トークンの追加はサポートされていません。
安全でない HTTP メソッドについてのみ偽造防止トークンを自動的に検証する
ASP.NET Core アプリでは、安全な HTTP メソッド (GET、HEAD、OPTIONS、TRACE) には偽造防止トークンは生成されません。
ValidateAntiForgeryToken 属性を広範に適用してから IgnoreAntiforgeryToken 属性でオーバーライドするのではなく、AutoValidateAntiforgeryToken 属性を使用できます。 この属性は、ValidateAntiForgeryToken 属性と同じように動作しますが、次の HTTP メソッドを使用して行われた要求についてはトークンを要求しない点が異なります。
- GET
- HEAD
- オプション
- TRACE
非 API のシナリオでは、AutoValidateAntiforgeryToken を広範に使うことをお勧めします。 この属性により、POST アクションが既定で保護されるようになります。 代わりの方法は、ValidateAntiForgeryToken が個々のアクション メソッドに適用されない限り、既定で偽造防止トークンを無視することです。 このシナリオでは、POST アクション メソッドが誤って保護されないままになり、アプリが CSRF 攻撃に対して脆弱になる可能性が高くなります。 すべての POST で、偽造防止トークンを送信する必要があります。
API には、トークンの cookie 以外の部分を送信するための自動メカニズムはありません。 実装は、クライアント コードの実装によって異なる場合があります。 いくつかの例を次に示します。
クラスレベルの例:
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
グローバルな例:
services.AddControllersWithViews(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
グローバルまたはコントローラーの偽造防止属性をオーバーライドする
IgnoreAntiforgeryToken フィルターは、特定のアクション (またはコントローラー) に対して偽造防止トークンを不要にするために使用されます。 このフィルターを適用すると、より高いレベル (グローバルまたはコントローラー上) で指定されている ValidateAntiForgeryToken および AutoValidateAntiforgeryToken フィルターがオーバーライドされます。
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
{
// no antiforgery token required
}
}
認証後にトークンを更新する
ユーザーが認証された後、ユーザーをビューまたは Razor Pages ページにリダイレクトすることによって、トークンを更新する必要があります。
JavaScript、AJAX、SPA
従来の HTML ベースのアプリでは、偽造防止トークンは非表示のフォーム フィールドを使用してサーバーに渡されます。 最新の JavaScript ベースのアプリと SPA では、多くの要求がプログラムによって行われます。 これらの AJAX 要求では、要求ヘッダーや cookie などの他の手法を使ってトークンを送信できます。
cookie が、認証トークンの格納と、サーバーでの API 要求の認証に使用されている場合、CSRF が問題になる可能性があります。 ローカル ストレージがトークンの格納に使われている場合は、ローカル ストレージの値はすべての要求でサーバーに自動的に送信されないため、CSRF の脆弱性が軽減される可能性があります。 ローカル ストレージを使用してクライアントに偽造防止トークンを格納し、要求ヘッダーとしてトークンを送信することをお勧めします。
JavaScript
JavaScript とビューを使うと、ビュー内からサービスを使ってトークンを作成できます。 ビューに IAntiforgery サービスを挿入し、GetAndStoreTokens を呼び出します。
@{
ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<div class="row">
<p><input type="button" id="antiforgery" value="Antiforgery"></p>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == XMLHttpRequest.DONE) {
if (xhttp.status == 200) {
alert(xhttp.responseText);
} else {
alert('There was an error processing the AJAX request.');
}
}
};
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("antiforgery").onclick = function () {
xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
xhttp.setRequestHeader("RequestVerificationToken",
document.getElementById('RequestVerificationToken').value);
xhttp.send();
}
});
</script>
</div>
この方法を使用すると、サーバーからの cookie の設定やクライアントからの読み取りを、直接処理する必要がなくなります
前の例では、JavaScript を使用して、AJAX POST ヘッダーの非表示フィールドの値を読み取っています。
また、JavaScript は Cookie 内のトークンにアクセスし、cookie の内容を使用してトークンの値を含むヘッダーを作成することもできます。
context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,
new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
X-CSRF-TOKEN という名前のヘッダーでトークンを送信することがスクリプトで要求されている場合は、X-CSRF-TOKEN ヘッダーを検索するように偽造防止サービスを構成します。
services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");
次の例では、JavaScript を使用して、適切なヘッダーで AJAX 要求を作成しています。
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
var csrfToken = getCookie("CSRF-TOKEN");
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (xhttp.readyState === XMLHttpRequest.DONE) {
if (xhttp.status === 204) {
alert('Todo item is created successfully.');
} else {
alert('There was an error processing the AJAX request.');
}
}
};
xhttp.open('POST', '/api/items', true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "name": "Learn C#" }));
AngularJS
AngularJS では、CSRF に対処するために規則が使用されます。 サーバーが cookie という名前で XSRF-TOKEN を送信した場合、AngularJS $http サービスにより、サーバーに要求が送信されるときに、ヘッダーに cookie の値が追加されます。 このプロセスは自動です。 クライアントは、ヘッダーを明示的に設定する必要はありません。 ヘッダーの名前は X-XSRF-TOKEN です。 サーバーでこのヘッダーを検出し、その内容を検証する必要があります。
アプリケーションの起動時に ASP.NET Core API でこの規則を処理するには:
-
cookie という名前の
XSRF-TOKENでトークンを提供するようにアプリを構成します。 -
X-XSRF-TOKENというヘッダーを探すように偽造防止サービスを構成します。これは、XSRF トークンを送信するための Angular の既定のヘッダー名です。
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
}
Note
要求ヘッダーとフォーム ペイロードの両方で偽造防止トークンが提供される場合、ヘッダー内のトークンのみが検証されます。
Windows 認証と偽造防止 cookie
Windows 認証を使用するときは、cookie の場合と同じ方法で、アプリケーション エンドポイントを CSRF 攻撃から保護する必要があります。 ブラウザーによって認証コンテキストがサーバーに暗黙的に送信されるため、エンドポイントを CSRF 攻撃から保護する必要があります。
偽造防止を強化する
IAntiforgeryAdditionalDataProvider 型を使用すると、開発者は、トークンごとに追加データをラウンドトリップすることで、CSRF 対策システムの動作を強化できます。 フィールド トークンを生成するたびに GetAdditionalData メソッドを呼び出し、戻り値を生成されるトークンに埋め込みます。 実装者は、タイムスタンプ、nonce、または他の値を返し、トークンの検証時に ValidateAdditionalData を呼び出してこのデータを検証できます。 クライアントのユーザー名は生成されたトークンに既に埋め込まれているので、この情報を含める必要はありません。 トークンに補足データが含まれていても、IAntiForgeryAdditionalDataProvider が構成されていない場合は、補足データは検証されません。
追加のリソース
ASP.NET Core