ASP.NET MVC と Web ページでの XSRF/CSRF の防止
作成者: Rick Anderson
クロスサイト リクエスト フォージェリ (XSRF または CSRF) は、Web でホストされるアプリケーションに対する攻撃であり、それによって悪意のある Web サイトが、クライアント ブラウザーとそのブラウザーが信頼する Web サイトの間のやり取りに影響を及ぼすことができます。 これらの攻撃が可能になるのは、要求ごとに Web ブラウザーが自動的に認証トークンを Web サイトに送信するからです。 標準的な例は、ASP.NET のフォーム認証チケットなどの認証クッキーです。 ただし、永続的な認証メカニズム (Windows 認証や基本認証など) を使用する Web サイトも、これらの攻撃の対象になることがあります。
XSRF 攻撃はフィッシング攻撃とは異なります。 フィッシング攻撃には攻撃対象とのやり取りが必要です。 フィッシング攻撃では、悪意のある Web サイトがターゲット Web サイトを模擬し、攻撃対象は重要な情報を攻撃者に提供するようにだまされます。 XSRF 攻撃では、多くの場合に攻撃対象とのやり取りは必要ありません。 むしろ、攻撃者はブラウザーがすべての関連クッキーを模擬 Web サイトに自動的に送信することに頼っています。
詳細については、「Open Web Application Security Project (OWASP)」および「XSRF」を参照してください。
攻撃の構造
XSRF 攻撃に関する説明として、オンライン バンキング取引を実行しようとするユーザーを考えましょう。 このユーザーは、最初に WoodgroveBank.com にアクセスしてログインします。その時点で、応答ヘッダーには認証 Cookie が含まれます。
HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }
認証 Cookie はセッション Cookie であるため、ブラウザー プロセスが終了すると、ブラウザーによって自動的にクリアされます。 ただし、その時点まで、ブラウザーが WoodgroveBank.com への要求を行うごとに、Cookie が自動的に含まれます。 ユーザーは 1,000 ドルを別のアカウントに転送する必要が生じ、銀行サイトのフォームに入力し、ブラウザーがサーバーにこの要求を行います。
POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00
この操作には副作用があるため (金銭トランザクションを開始します)、銀行サイトは、この操作の開始には HTTP POST を要求するようにしています。 サーバーは要求から認証トークンを読み取り、現在のユーザーのアカウント番号を検索し、十分な資金が存在することを確認してから、宛先アカウントへのトランザクションを開始します。
オンライン バンキングは完了し、ユーザーは銀行サイトから離れ、Web の他の場所にアクセスします。 これらのサイトの 1 つ (fabrikam.com) には、<iframe> 内に埋め込まれたページに次のマークアップが含まれています。
<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
<input type="hidden" name="toAcct" value="67890" />
<input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
document.getElementById('theForm').submit();
</script>
これにより、ブラウザーがこの要求を行います。
POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00
攻撃者は、ユーザーがまだターゲット Web サイトの有効な認証トークンを持っている可能性があることを悪用しており、Javascript の小さなスニペットを使用して、ブラウザーがターゲット サイトに HTTP POST を自動的に行うようにしています。 認証トークンがまだ有効な場合、銀行サイトは攻撃者が選択したアカウントに 250 ドルの転送を開始します。
効果のない軽減策
上記のシナリオでは、WoodgroveBank.com が SSL 経由でアクセスされ、SSL 専用認証 Cookie を使用していたとことは、攻撃を阻止するには不十分であることに注意してください。 攻撃者は自分の<フォーム>要素で URI スキーム (https) を指定することができ、Cookie が目的のターゲットの URI スキーム と一致している限り、ブラウザーは依然として、それらの期限切れでない Cookie をターゲット サイトに送信します。
信頼されたサイトのみを訪問すると、オンラインの安全が保たれるので、ユーザーは信頼されていないサイトを訪問しないだけでよいという考えもあり得ます。 これにはある程度真ですが、残念ながら、このアドバイスは必ずしも実用的ではありません。 ユーザーはローカル ニュース サイト ConsolidatedMessenger.com を '信頼' し、代わりにそのサイトにアクセスすることにしますが、そのサイトには XSS の脆弱性があるため、攻撃者は fabrikam.com で実行されていたのと同じコードスニペットを挿入できます。
受信要求に、ドメインを参照する Referer ヘッダーがあることを確認します。 これにより、サード パーティのドメインから無意識のうちに送信された要求を停止することができます。 ただし、プライバシー上の理由からブラウザーの Referer ヘッダーを無効にしている人もいます。また、被害者が何らかの安全ではないソフトウェアをインストールしている場合、攻撃者はそのヘッダーをスプーフィングする可能性があります。 Referer ヘッダーの 検証は、XSRF 攻撃を防ぐための安全なアプローチとは見なされません。
Web Stack Runtime での XSRF 軽減策
ASP.NET Web Stack Runtime では、シンクロナイザー トークン パターンのバリアントを使用して XSRF 攻撃から防御します。 シンクロナイザー トークン パターンの一般的な形式では、2 つの XSRF 対策トークンが各 HTTP POST (認証トークンに加えて) を使用してサーバーに送信されます。1 つのトークンは Cookie として、もう 1 つはフォーム値として送信されます。 ASP.NET ランタイムによって生成されたトークン値は、攻撃者によって判断または予測できないものです。 トークンが送信されると、サーバーは両方のトークンが比較チェックに合格した場合にのみ要求の続行を許可します。
XSRF 要求を検証するセッション トークン は HTTP Cookie として格納され、現在、ペイロードに次の情報が含まれています。
- ランダムな 128 ビット識別子で構成されるセキュリティ トークン。
次の図は、Internet Explorer F12 開発者ツールで表示される XSRF 要求検証セッション トークンを示しています (これは現在の実装であり、変更される可能性があります)。
フィールド トークン は <input type="hidden" />
として格納され、ペイロードに次の情報が含まれます。
- ログインしているユーザーのユーザー名 (認証されている場合)。
- IAntiForgeryAdditionalDataProvider によって提供される追加データ。
XSRF 対策トークンのペイロードは暗号化および署名されているため、ツールを使用してトークンを調べてもユーザー名を表示することはできません。 Web アプリケーションが ASP.NET 4.0 を対象としている場合、暗号化サービスは MachineKey.Encode ルーチンによって行われます。 Web アプリケーションが ASP.NET 4.5 以降を対象としている場合、暗号化サービスは MachineKey.Protect ルーチンによって行われ、より高いパフォーマンス、拡張性、およびセキュリティが得られます。 詳細については、次のブログ記事を参照してください。
トークンの生成
XSRF 対策トークンを生成するには、MVC ビューまたは Razor ページの @AntiForgery.GetHtml() から @Html.AntiForgeryToken メソッドを呼び出します。 その後、ランタイムは次の手順を実行します。
- 現在の HTTP 要求に既に XSRF 対策セッション トークン (XSRF 対策 Cookie __RequestVerificationToken) が含まれている場合は、セキュリティ トークンがそこから抽出されます。 HTTP 要求に XSRF 対策セッション トークンが含まれていない場合、またはセキュリティ トークンの抽出に失敗した場合は、新しくランダムな XSRF 対策トークンが生成されます。
- XSRF 対策フィールド トークンは、上記の手順 (1) のセキュリティ トークンと、現在ログインしているユーザーの ID を使用して生成されます。 (ユーザー ID の判断について詳しくは、以下の「特別なサポートでのシナリオ」セクションを参照してください)。さらに、IAntiForgeryAdditionalDataProvider が構成されている場合 、ランタイムはその GetAdditionalData メソッドを呼び出し、返された文字列をフィールド トークンに含めます。 (詳細については、「構成と拡張性」セクションを参照してください)。
- 手順 (1) で新しい XSRF 対策トークンが生成された場合、それを含む新しいセッション トークンが作成され、送信 HTTP Cookie コレクションに追加されます。 ステップ (2) のフィールド トークンは
<input type="hidden" />
要素にラップされ、この HTML マークアップの戻り値はHtml.AntiForgeryToken()
またはAntiForgery.GetHtml()
となります。
トークンの検証
受信した XSRF 対策トークンを検証するために、開発者は MVC アクションまたはコントローラーに ValidateAntiForgeryToken 属性を含めるか、@AntiForgery.Validate()
を Razor ページから呼び出します。 ランタイムは、次の手順を実行します。
- 受信セッション トークンとフィールド トークンが読み取られ、それぞれから XSRF 対策トークンが抽出されます。 XSRF 対策トークンは、生成ルーチンのステップ (2) のそれぞれで同じである必要があります。
- 現在のユーザーが認証されている場合、ユーザー名はフィールド トークンに格納されているユーザー名と比較されます。 ユーザー名は一致する必要があります。
- IAntiForgeryAdditionalDataProvider が構成されている場合、ランタイムは ValidateAdditionalData メソッドを呼び出します。 このメソッドはブール値 trueを返す必要があります。
検証が成功した場合、ユーザーは続行できます。 検証に失敗した場合、フレームワークは HttpAntiForgeryException をスロー します。
エラーとなる条件
ASP.NET Web Stack Runtime v2 以降では、検証中にスローされるすべての HttpAntiForgeryException に、問題の詳細な情報が含まれます。 現在定義されているエラー条件は次のとおりです。
- セッション トークンまたはフォーム トークンが要求に存在しません。
- セッション トークンまたはフォーム トークンを読み取れません。 最も可能性の高い原因は、ファームが ASP.NET Web Stack Runtime の一致しないバージョンを実行しているか、ファームの Web.config 内の <machineKey> がマシン間で異なっていることです。 Fiddler などのツールを使用して、いずれかの XSRF 対策トークンを改ざんすることで、この例外を起こすことができます。
- セッション トークンとフィールド トークンがスワップされました。
- セッション トークンとフィールド トークンに、不一致のセキュリティ トークンが含まれています。
- フィールド トークン内に埋め込まれたユーザー名が、現在ログインしているユーザーのユーザー名と一致しません。
- IAntiForgeryAdditionalDataProvider.ValidateAdditionalData メソッドが false を返しました。
XSRF 対策機能は、トークンの生成または検証中に追加のチェックを実行する場合もあり、これらのチェック中にエラーが発生すると、例外がスローされる可能性があります。 詳細については、「WIF/ACS/クレーム ベースの認証」および「構成と拡張性」セクションを参照してください。
特別なサポートのあるシナリオ
匿名認証
XSRF 対策システムには、匿名ユーザーに対する特別なサポートが含まれています。"anonymous" は、IIdentity.IsAuthenticated プロパティが false を返すユーザーとして定義されています。 シナリオには、(ユーザーが認証される前に) ログイン ページに XSRF 保護を用意することや、アプリケーションが IIdentity 以外のメカニズムを使用してユーザーを識別するカスタム認証スキームが含まれます。
これらのシナリオをサポートするために、セッション トークンとフィールド トークンは、128 ビットのランダムに生成された不透明な識別子であるセキュリティ トークンによって結合されていることを思い出してください。 このセキュリティ トークンは、サイト内を移動するときに個々のユーザーのセッションを追跡するために使用されるため、匿名識別子の用途に効果的です。 空の文字列は、前述の生成ルーチンと検証ルーチンで、ユーザー名の代わりに使用されます。
WIF/ACS/クレーム ベース認証
通常、.NET Framework に組み込まれている IIdentity クラスには、特定のアプリケーション内の特定のユーザーを一意に識別するのに十分な IIdentity.Name プロパティがあります。 たとえば、 FormsIdentity.Name はメンバーシップ データベースに格納されているユーザー名 (そのデータベースに依存するすべてのアプリケーションで一意) を返し、WindowsIdentity.Name はユーザーのドメイン修飾 ID を返します。 これらのシステムは認証を行うのに加え、アプリケーションに対するユーザーを識別します。
一方、クレーム ベースの認証では、必ずしも特定のユーザーを識別する必要はありません。 代わりに、 ClaimsPrincipal 型と ClaimsIdentity 型は、クレーム インスタンスのセットに関連付けられます。個々の要求は、たとえば "18 歳以上"、"管理者" などになります。 ユーザーは必ずしも識別されていないので、ランタイムはこの特定のユーザーの一意識別子としては ClaimsIdentity.Name プロパティを使用できません。 チームは、ClaimsIdentity.Name が null、フレンドリ名 (表示名) 名、またはユーザーの一意識別子として使用するのに適していない文字列を返す、実際の例を見てきました。
クレーム ベース認証を使用するデプロイの多くは、特に Azure Access Control Service (ACS) を使用 しています。 ACS を使用すると、開発者は個々の ID プロバイダー (Microsoft アカウント プロバイダー ADFS、Yahoo!などの OpenID プロバイダーなど) を構成でき、ID プロバイダーは名前識別子を返します。 これらの名前識別子に電子メール アドレスなどの個人を特定できる情報 (PII) が含まれている場合も、プライベート個人識別子 (PPID) のように匿名化される場合もあります。 いずれの場合も、タプル (ID プロバイダー、名前識別子) は、サイトの閲覧中に特定のユーザーに対して適切な追跡トークンとして十分に機能するため、ASP.NET Web Stack Runtime は、XSRF 対策フィールド トークンを生成して検証するときに、ユーザー名の代わりにタプルを使用できます。 ID プロバイダーと名前識別子の URI は次のとおりです。
https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
(詳細については「ACS ドキュメント ページ」を参照してください)。
トークンを生成または検証するときに、ASP.NET Web Stack ランタイムは実行時に型へのバインドを試みます。
Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
(WIF SDK 用)。System.Security.Claims.ClaimsIdentity
(.NET 4.5 用)。
これらの型が存在し、現在のユーザーの IIIIdentity がこれらの型のいずれかを実装またはサブクラス化する場合、XSRF 対策機能では、トークンの生成と検証時にユーザー名の代わりに (ID プロバイダー、名前識別子) タプルが使用されます。 このようなタプルが存在しない場合、要求は失敗し、XSRF 対策システムを構成し、使用される特定のクレーム ベースの認証メカニズムを把握できるよう、開発者にエラーを表示します。 詳細については、「構成と拡張性」セクションを参照してください。
OAuth/OpenID 認証
最後に、XSRF 対策機能は、OAuth または OpenID 認証を使用するアプリケーションに対して特別なサポートを行います。 このサポートはヒューリスティックベースです。現在の IIdentity.Name が http:// または https:// で始まる場合、ユーザー名の比較は、既定の OrdinalIgnoreCase 比較子ではなく序数比較子を使用して行われます。
構成と拡張性
開発者が、XSRF 対策の生成と検証の動作をより厳密に制御する必要のある場合があります。 たとえば、MVC および Web ページ ヘルパーが応答に HTTP Cookie を自動的に追加する既定の動作は望ましくなく、開発者はトークンを別の場所に保持したい場合があります。 これを支援する 2 つの API があります。
AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);
GetTokens メソッドは、既存の XSRF 要求検証セッション トークン (null である場合があります) を入力として受け取り、新しい XSRF 要求検証セッション トークンとフィールド トークンを出力として生成します。 端的には、トークンは装飾のない不透明な文字列です。formToken 値は、<入力>タグにラップされません。 newCookieToken 値は null である可能性があります。その場合も oldCookieToken 値は有効であり、新しい応答 Cookie を設定する必要はありません。 GetTokens の 呼び出し元が、必要な応答 Cookie を保持したり、必要なマークアップを生成したりする役割を果たします。GetTokens メソッド自体では、副作用として応答は変更されません。 Validate メソッドは受信セッションとフィールド トークンを受け取り、それらに対して前述の検証ロジックを実行します。
AntiForgeryConfig
開発者は、Application_Start から非 XSRF システムを構成できます。 構成はプログラムによって行われます。 静的 AntiForgeryConfig 型のプロパティを以下に示します。 クレームを使用するほとんどのユーザーは、UniqueClaimTypeIdentifier プロパティを設定することが望ましいです。
プロパティ | 説明 |
---|---|
AdditionalDataProvider | トークンの生成中に追加のデータを提供し、トークンの検証中に追加のデータを使用する IAntiForgeryAdditionalDataProvider。 既定値は null です。 詳細については、「IAntiForgeryAdditionalDataProvider」セクションを参照してください。 |
CookieName | XSRF 対策セッション トークンの格納に使用される HTTP Cookie の名前を指定する文字列。 この値が設定されていない場合、アプリケーションのデプロイされた仮想パスに基づいて名前が自動的に生成されます。 既定値は null です。 |
RequireSsl | SSL で保護されたチャネル経由で XSRF 対策トークンを送信する必要があるかどうかを示すブール値。 この値が true の場合、自動的に生成された Cookie には "安全" フラグが設定され、SSL 経由で送信されない要求内から呼び出されると、XSRF 対策 API がスローされます。 既定値は false です。 |
SuppressIdentityHeuristicChecks | XSRF 対策システムがクレーム ベースの ID のサポートを無効にするかどうかを示すブール値。 この値が true の 場合、システムは、IIdentity.Name が一意のユーザーごとの識別子として使用するのに適していると想定し、「WIF/ACS/claims-based authentication」セクションで説明されているように、IClaimsIdentity または ClClaimsIdentity に特殊な対応を試みません。 既定値は false です。 |
UniqueClaimTypeIdentifier | 一意のユーザーごとの識別子として使用するために適切な要求の種類を示す文字列。 この値が設定され、現在の IIdentity がクレーム ベースの場合、システムは UniqueClaimTypeIdentifier で指定された型の要求を抽出しようとします。対応する値は、フィールド トークンの生成時にユーザーのユーザー名の代わりに使用されます。 要求の種類が見つからない場合、システムは要求を通しません。 既定値は null です。これは、先述の通り、ユーザーのユーザー名の代わりに、システムが (ID プロバイダー、名前識別子) タプルを使用するようになることを示します。 |
IAntiForgeryAdditionalDataProvider
IAntiForgeryAdditionalDataProvider 型を使用すると、開発者は、トークンごとに追加データをラウンドトリップすることで、CSRF 対策システムの動作を強化できます。 フィールド トークンを生成するたびに GetAdditionalData メソッドを呼び出し、戻り値を生成されるトークンに埋め込みます。 実装者は、タイムスタンプ、nonce、またはこのメソッドから求めるその他の値を返すことができます。
同様に、 フィールド トークンが検証されるたびに ValidateAdditionalData メソッドが呼び出され、トークン内に埋め込まれた "追加データ" 文字列がメソッドに渡されます。 検証ルーチンには、タイムアウト (そうするには、トークンの作成時に格納された時刻に対して現在の時刻をチェックします)、nonce チェック ルーチン、またはその他の必要なロジックを実装できます。
設計上の決定とセキュリティに関する考慮事項
セッションおよびフィールド トークンをリンクするセキュリティ トークンは、技術的には、XSRF 攻撃から匿名または認証されていないユーザーを保護する場合にのみ必要です。 ユーザーが認証されると、認証トークン自体 (Cookie の形式で送信されると想定されます) をシンクロナイザー トークン ペアの片側として使用できます。 ただし、認証されていないユーザーによってヒットしたログイン ページを保護するための有効なシナリオがあります。認証されたユーザーに対しても、セキュリティ トークンを常に生成して検証することで、XSRF 対策ロジックが簡単になりました。 また、フィールド トークンが攻撃者によって侵害された場合に、攻撃者に対し、セッション トークンを設定または推測するというもう 1 つのハードルがあるため、さらなる保護も得られます。
開発者は、複数のアプリケーションが 1 つのドメインでホストされている場合に注意する必要があります。 たとえば、example1.cloudapp.net と example2.cloudapp.net が異なるホストであっても、*.cloudapp.net ドメインのすべてのホスト間に暗黙的な信頼関係があります。 この暗黙的な信頼関係により、信頼できないホストが互いの Cookie に影響を与える可能性があります (AJAX 要求を管理する同一オリジン ポリシーは、必ずしもHTTP Cookie に適用されるとは限りません)。 ASP.NET Web Stack ランタイムは、ユーザー名がフィールド トークンに埋め込まれるという意味で軽減策となります。悪意のあるサブドメインによってセッション トークンを上書きできる場合でも、ユーザーの有効なフィールド トークンは生成できません。 ただし、このような環境でホストされている場合でも、組み込みの XSRF 対策ルーチンはセッション ハイジャックやログイン XSRF を防げません。
現在、XSRF 対策ルーチンではクリックジャッキングを防げません。 アプリケーションでクリックジャッキングを防ぐには、各応答で X-Frame-Options: SAMEORIGIN ヘッダーを送信することで簡単に行うことができます。 このヘッダーは、すべての最新のブラウザーでサポートされています。 詳細については、IE ブログ、SDL ブログ、OWASP を参照してください 。 ASP.NET Web Stack ランタイムの将来のリリースで、MVC および Web ページの XSRF 対策ヘルパーがこのヘッダーを自動的に設定して、アプリケーションがこの攻撃から自動的に保護されるようになる可能性があります。
Web 開発者は、サイトが XSS 攻撃に対して脆弱でないことを引き続き確認する必要があります。 XSS 攻撃は非常に強力であり、悪用が成功すると、XSRF 攻撃に対する ASP.NET Web Stack ランタイムの防御も損なわれます。
受信確認
@LeviBroderick は、この情報の大部分において ASP.NET セキュリティ コードの多くを記述したユーザーです。