ビュー ステートとは
ビュー ステートは、ASP.NET アプリケーション内の WebForms (.aspx) ページ間で丸く切り取られた情報です。 __VIEWSTATE フィールドの HTML マークアップは次のようになります。
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..." />
__VIEWSTATE フィールドに格納される可能性がある項目の 1 つの例は、Button コントロールのテキストです。 ユーザーがボタンをクリックすると、Button_Click イベント ハンドラーは、ビューステート フィールドから Button のテキストを抽出できます。
ASP.NET ビューステートの詳細な概要については、Microsoft Developer Network (MSDN) Web サイトの ASP.NET ビュー ステートの概要に関するトピックを参照してください。
__VIEWSTATE フィールドには、ポストバックでページを再構築するために使用される重要な情報が含まれているため、攻撃者がこのフィールドを改ざんできないようにしてください。 攻撃者が悪意のある__VIEWSTATEペイロードを送信した場合、攻撃者はアプリケーションをだまして、それ以外の場合は実行できなかったアクションを実行する可能性があります。
この種の改ざん攻撃を防ぐために、__VIEWSTATE フィールドはメッセージ認証コード (MAC) によって保護されます。 ASP.NET は、ポストバックが発生したときに__VIEWSTATE ペイロードと共に送信される MAC を検証します。 MAC の計算に使用されるキーは、Web.config ファイル内のアプリケーションの 要素 で指定されます。 攻撃者は <machineKey> 要素の内容を推測できないため、攻撃者が__VIEWSTATEペイロードを改ざんしようとすると、有効な MAC を提供できません。 ASP.NET は有効な MAC が提供されていないことを検出し、ASP.NET は悪意のある要求を拒否します。
MAC 検証エラーの原因
MAC 検証エラーは次の例のようになります。
注
'/' アプリケーションのサーバー エラー。
ビューステート MAC の検証に失敗しました。 このアプリケーションが Web ファームまたはクラスターによってホストされている場合は、 <machineKey> 構成で同じ validationKey と検証アルゴリズムが指定されていることを確認します。 AutoGenerate はクラスターでは使用できません。
説明: 現在の Web 要求の実行中に未処理の例外が発生しました。 エラーに関する詳細および例外の発生場所については、スタック トレースを参照してください。
例外の詳細: System.Web.HttpException: ビューステート MAC の検証に失敗しました。 このアプリケーションが Web ファームまたはクラスターによってホストされている場合は、 <machineKey> 構成で同じ validationKey と検証アルゴリズムが指定されていることを確認します。 AutoGenerate はクラスターでは使用できません。
ソース エラー: [関連するソース行なし]
ソース ファイル: ...行: 0
スタック トレース:
[ViewStateException: 無効なビューステート。
クライアント IP: ::1
ポート: 40653
リファラー: http://localhost:40643/MyPage.aspx
パス: /MyPage.aspx
User-Agent: Mozilla/5.0 (互換性あり;MSIE 10.0;Windows NT 6.2;WOW64;Trident/6.0)
ViewState: ...]
[HttpException (0x80004005): ビューステート MAC の検証に失敗しました。 このアプリケーションが Web ファームまたはクラスターによってホストされている場合は、 <machineKey> 構成で同じ validationKey と検証アルゴリズムが指定されていることを確認します。 AutoGenerate はクラスターでは使用できません。
詳細については、「http://go.microsoft.com/fwlink/?LinkID=314055」を参照してください。
System.Web.UI.ViewStateException.ThrowError(Exception inner, String persistedState, String errorPageMessage, Boolean macValidationError) +190
System.Web.UI.ViewStateException.ThrowMacValidationError(Exception inner, String persistedState) +46
System.Web.UI.ObjectStateFormatter.Deserialize(String inputString, Purpose purpose) +861
System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter2.Deserialize(String serializedState, Purpose purpose) +51
System.Web.UI.Util.DeserializeWithAssert(IStateFormatter2 フォーマッタ, String serializedState, Purpose purpose) +67
System.Web.UI.HiddenFieldPageStatePersister.Load() +444
System.Web.UI.Page.LoadPageStateFromPersistenceMedium() +368
System.Web.UI.Page.LoadAllState() +109
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +7959
System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +429
System.Web.UI.Page.ProcessRequest() +125
System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) +48
System.Web.UI.Page.ProcessRequest(HttpContext コンテキスト) +234
ASP.mypage_aspx。... の ProcessRequest(HttpContext コンテキスト):0
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +1300
System.Web.HttpApplication.ExecuteStep(IExecutionStep ステップ, Boolean& completedSynchronously) +140
原因 1: Web アプリケーションがファーム (マルチサーバー環境) で実行されている
ASP.NET は、アプリケーションごとに暗号化キーを自動的に生成し、そのキーを HKCU レジストリ ハイブに格納します。 この自動生成されたキーは、アプリケーションの構成に明示的な <machineKey> 要素がない場合に使用されます。 ただし、この自動生成されたキーはキーを作成したコンピューターに対してローカルであるため、このシナリオではファームで実行されるアプリケーションに問題が発生します。 ファーム内の各サーバーは独自のローカル キーを生成し、ファーム内のどのサーバーも使用するキーに同意しません。 その結果、あるサーバーが別のサーバーが使用する__VIEWSTATEペイロードを生成すると、コンシューマーは MAC 検証エラーが発生します。
解決策 1a: 明示的な <machineKey> 要素を作成する
明示的な <machineKey> 要素をアプリケーションの Web.config ファイルに追加することで、開発者は自動生成された暗号化キーを使用しないように ASP.NET に指示します。 <machineKey> 要素を生成する方法については、「付録 A」を参照してください。 この要素を Web.config ファイルに追加したら、ファーム内の各サーバーにアプリケーションを再デプロイします。
注 Microsoft Azure Web サイトなどの一部の Web ホスティング サービスでは、バックエンド サーバー間で各アプリケーションの自動生成されたキーを同期する手順を実行します。 これにより、明示的な <machineKey> 要素を指定していないアプリケーションは、アプリケーションがファームで実行されている場合でも、これらの環境での作業を続行できます。 アプリケーションがサード パーティのホスティング サービスで実行されている場合は、ホスティング プロバイダーに問い合わせて、この状況が適用されるかどうかを判断してください。
解決策 1b: ロード バランサーでアフィニティを有効にする
サイトがロード バランサーの背後で動作している場合は、サーバー アフィニティを有効にして問題を一時的に回避できます。 これにより、特定のクライアントがロード バランサーの背後にある 1 つの物理サーバーとのみ対話し、すべての暗号化ペイロードが同じサーバーによって生成され、同じサーバーによって使用されるようにすることができます。
これは、問題に対する長期的な解決策と見なすべきではありません。 サーバー アフィニティが有効になっている場合でも、ロード バランサーがアフィニティされた元のサーバーがオフラインになった場合、ほとんどのロード バランサーはクライアントを別の物理サーバーにリダイレクトします。 これにより、新しいサーバーは、クライアントが現在持っている暗号化ペイロード (__VIEWSTATE、フォーム認証チケット、MVC 偽造防止トークン、その他のサービスなど) を拒否します。
明示的な <machineKey> 要素を使用し、アプリケーションを再デプロイする方が、サーバー アフィニティを有効にするよりも優先されます。
原因 2: ワーカー プロセスで IIS 7.0 アプリケーション プール ID が使用される
インターネット インフォメーション サービス (IIS) 7.0 (Windows Vista、Windows Server 2008) では、ASP.NET アプリケーションを実行するサーバーのセキュリティ強化に役立つ新しい分離メカニズムであるアプリケーション プール ID が導入されました。 ただし、アプリケーション プール ID の下で実行されているサイトは、HKCU レジストリにアクセスできません。 ここで、ASP.NET ランタイムは自動生成された <machineKey> キーを格納します。 その結果、ASP.NET アプリケーション プールがリセットされたときに自動生成されたキーを保持できません。 そのため、w3wp.exe がリセットされるたびに、新しい一時キーが生成されます。
注 IIS 7.5 (Windows 7、Windows Server 2008 R2) 以降のバージョンでは問題ではありません。 これらのバージョンの IIS では、ASP.NET は、自動生成されたキーを、アプリケーション プールのリセット後も存続する別の場所に保持できます。
解決策 2a: aspnet_regiis ユーティリティを使用する
ASP.NET インストールには、ユーティリティが含まれています 。aspnet_regiis.exe。 このユーティリティを使用すると、ASP.NET IIS とのインターフェイスで、マネージド アプリケーションの実行に必要な構成を実行できます。 これらの構成の 1 つは、自動生成されたマシン キーの永続化を有効にするために必要なキーをレジストリ ハイブに作成します。
まず、サイトが使用しているアプリケーション プールを決定する必要があります。 これは、IIS に含まれる inetmgr ユーティリティを使用して判断できます。 左側のツリー ビューでサイトを選択し、[ Website の管理] を右クリックし、[ 詳細設定] をクリックします。 表示されるダイアログ ボックスに、アプリケーション プール名が表示されます。
ASP.NET 4.0 アプリケーション プールの適切なレジストリ キーをスキャフォールディングするには、次の手順に従います。
管理コマンド プロンプトを開きます。
アプリケーション プールが 32 ビットか 64 ビットかに応じて、適切なディレクトリを見つけます。
- 32 ビット アプリケーション プール: cd /d %windir%\Microsoft.NET\Framework\v4.0.30319
- 64 ビット アプリケーション プール: cd /d %windir%\Microsoft.NET\Framework64\v4.0.30319
ディレクトリに移動し、次のコマンドを入力し、Enter キーを押します。
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
アプリケーション プールが ASP.NET 2.0 または 3.5 アプリケーション プールの場合は、次の手順に従います。
管理コマンド プロンプトを開きます。
アプリケーション プールが 32 ビットか 64 ビットかに応じて、適切なディレクトリを見つけます。
- 32 ビット アプリケーション プール: cd /d %windir%\Microsoft.NET\Framework\v2.0.50727
- 64 ビット アプリケーション プール: cd /d %windir%\Microsoft.NET\Framework64\v2.0.50727
ディレクトリに移動し、次のコマンドを入力し、Enter キーを押します。
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
たとえば、アプリケーション プールの名前が [マイ アプリ プール] の場合 (前のイメージと同様)、次のコマンドを実行します。
aspnet_regiis -ga "IIS APPPOOL\My App Pool"注 IIS APPPOOL\* 名を適切に解決するには、aspnet_regiis ユーティリティに対してシステム サービス APPHOSTSVC と WAS が実行されている必要があります。
解決策 2b: 明示的な <machineKey> 要素を作成する
明示的な <machineKey> 要素をアプリケーションの Web.config ファイルに追加することで、開発者は自動生成された暗号化キーを使用しないように ASP.NET に指示します。 <machineKey> 要素を生成する方法については、「付録 A」を参照してください。
原因 3: アプリケーション プールが LoadUserProfile=false を使用して構成されている
アプリケーション プールがカスタム ID で実行されている場合、IIS によって ID のユーザー プロファイルが読み込まれていない可能性があります。 これは、自動生成された <machineKey> を保持するために、ASP.NET で HKCU レジストリを使用できないようにする副作用があります。 そのため、アプリケーションが再起動されるたびに、新しい自動生成キーが作成されます。 詳細については、Microsoft Web サイトの 「ユーザー プロファイル」 セクションを参照してください。
解決策 3a: aspnet_regiis ユーティリティを使用する
この手順は 、解像度 2a と同じです。 詳細については、そのセクションを参照してください。
解決策 3b: 明示的な <machineKey を使用する>
明示的な <machineKey> 要素をアプリケーションの Web.config ファイルに追加することで、開発者は自動生成された暗号化キーを使用しないように ASP.NET に指示します。 <machineKey> 要素を生成する方法については、「付録 A」を参照してください。
解決策 3c: 必要な HKCU レジストリ キーを手動でプロビジョニングする
aspnet_regiis ユーティリティを実行できない場合は、Windows PowerShell スクリプトを使用して HKCU で適切なレジストリ キーをプロビジョニングできます。 詳細については、「 付録 B 」を参照してください。
解決策 3d: このアプリケーション プールに LoadUserProfile=true を設定する
このアプリケーション プール内でユーザー プロファイルの読み込みを有効にすることもできます。 これにより、HKCU レジストリ ハイブ、一時フォルダー、およびその他のユーザー固有のストレージの場所がアプリケーションで使用できるようになります。 ただし、これにより、ワーカー プロセスのディスクまたはメモリ使用量が増加する可能性があります。 この設定を有効にする方法の詳細については、 要素 を参照してください。
原因 4: Page.ViewStateUserKey プロパティの値が正しくありません
ソフトウェア開発者は、Page.ViewStateUserKey プロパティを使用して、クロスサイト要求フォージェリ保護を __VIEWSTATE フィールドに追加できます。 Page.ViewStateUserKey プロパティを使用する場合は、通常、現在のユーザーのユーザー名やユーザーのセッション識別子などの値に設定されます。 Microsoft Visual Studio 2012 以降のバージョンの WebForms アプリケーションのプロジェクト テンプレートには、このプロパティを使用するサンプルが含まれています。 詳細については、Microsoft Developer Network (MSDN) Web サイトの Page.ViewStateUserKey プロパティ に関するトピックを参照してください。
ViewStateUserKey プロパティが指定されている場合、その値は生成時に__VIEWSTATEに書き込まれます。 __VIEWSTATE フィールドが使用されると、サーバーは現在の Page の ViewStateUserKey プロパティをチェックし、__VIEWSTATE フィールドの生成に使用された値と照合して検証します。 値が一致しない場合、要求は悪意のある可能性があるものとして拒否されます。
ViewStateUserKey 関連のエラーの例としては、ブラウザーで 2 つのタブが開いているクライアントがあります。 クライアントはユーザー A としてログインし、最初のタブでは、ViewStateUserKey プロパティに "User A" が含まれる__VIEWSTATEで Page がレンダリングされます。2 番目のタブでは、クライアントがログアウトし、ユーザー B として再度ログインします。クライアントは最初のタブに戻り、フォームを送信します。 ViewStateUserKey プロパティに "User B" が含まれている場合があります (これはクライアントの認証 Cookie が示しているためです)。 ただし、クライアントが送信した__VIEWSTATE フィールドには、"ユーザー A" が含まれています。この不一致が原因でエラーが発生します。
解決策 4a: ViewStateUserKey が正しく設定されていることを確認する
アプリケーションで ViewStateUserKey プロパティを使用する場合は、ビュー ステートが生成されたときと使用されたときの両方で、プロパティの値が同じであることを確認します。 現在ログインしているユーザーのユーザー名を使用している場合は、ユーザーがまだログインしていること、およびポストバック時にユーザーの ID が変更されていないことを確認します。 現在のユーザーのセッション識別子を使用している場合は、セッションがタイムアウトしていないことを確認します。
ファーム環境で実行している場合は、 <machineKey> 要素が一致していることを確認します。 これらの要素を生成する方法については、「 付録 A」 を参照してください。
付録 A: <machineKey> 要素を生成する方法
注
セキュリティの警告
ボタンをクリックすると、 <machineKey> 要素が生成される Web サイトが多数あります。 これらのサイトから取得した <machineKey> 要素は使用しないでください。 これらのキーが安全に作成されたかどうか、またはシークレット データベースに記録されているかどうかはわかりません。 自分で作成した <machineKey> 構成要素のみを使用する必要があります。
<machineKey>要素を自分で生成するには、次のWindows PowerShellスクリプトを使用します。
# Generates a <machineKey> element that can be copied + pasted into a Web.config file.
function Generate-MachineKey {
[CmdletBinding()]
param (
[ValidateSet("AES", "DES", "3DES")]
[string]$decryptionAlgorithm = 'AES',
[ValidateSet("MD5", "SHA1", "HMACSHA256", "HMACSHA384", "HMACSHA512")]
[string]$validationAlgorithm = 'HMACSHA256'
)
process {
function BinaryToHex {
[CmdLetBinding()]
param($bytes)
process {
$builder = new-object System.Text.StringBuilder
foreach ($b in $bytes) {
$builder = $builder.AppendFormat([System.Globalization.CultureInfo]::InvariantCulture, "{0:X2}", $b)
}
$builder
}
}
switch ($decryptionAlgorithm) {
"AES" { $decryptionObject = new-object System.Security.Cryptography.AesCryptoServiceProvider }
"DES" { $decryptionObject = new-object System.Security.Cryptography.DESCryptoServiceProvider }
"3DES" { $decryptionObject = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider }
}
$decryptionObject.GenerateKey()
$decryptionKey = BinaryToHex($decryptionObject.Key)
$decryptionObject.Dispose()
switch ($validationAlgorithm) {
"MD5" { $validationObject = new-object System.Security.Cryptography.HMACMD5 }
"SHA1" { $validationObject = new-object System.Security.Cryptography.HMACSHA1 }
"HMACSHA256" { $validationObject = new-object System.Security.Cryptography.HMACSHA256 }
"HMACSHA385" { $validationObject = new-object System.Security.Cryptography.HMACSHA384 }
"HMACSHA512" { $validationObject = new-object System.Security.Cryptography.HMACSHA512 }
}
$validationKey = BinaryToHex($validationObject.Key)
$validationObject.Dispose()
[string]::Format([System.Globalization.CultureInfo]::InvariantCulture,
"<machineKey decryption=`"{0}`" decryptionKey=`"{1}`" validation=`"{2}`" validationKey=`"{3}`" />",
$decryptionAlgorithm.ToUpperInvariant(), $decryptionKey,
$validationAlgorithm.ToUpperInvariant(), $validationKey)
}
}
ASP.NET 4.0 アプリケーションの場合は、パラメーターを指定せずに Generate-MachineKey を呼び出すだけで、次のように <machineKey> 要素を生成できます。
PS> Generate-MachineKey
<machineKey decryption="AES" decryptionKey="..." validation="HMACSHA256" validationKey="..." />
ASP.NET 2.0 および 3.5 アプリケーションは、HMACSHA256をサポートしていません。 代わりに、SHA1 を指定して、互換性のある <machineKey> 要素を次のように生成できます。
PS> Generate-MachineKey -validation sha1
<machineKey decryption="AES" decryptionKey="..." validation="SHA1" validationKey="..." />
<machineKey> 要素があるとすぐに、Web.config ファイルに配置できます。 <machineKey> 要素は、アプリケーションのルートにある Web.config ファイルでのみ有効であり、サブフォルダー レベルでは有効ではありません。
<configuration>
<system.web>
<machineKey ... />
</system.web>
</configuration>
サポートされているアルゴリズムの完全な一覧については、Windows PowerShell プロンプトからヘルプ Generate-MachineKey を実行します。
付録 B: 自動生成されたキーを保持するためのレジストリのプロビジョニング
既定では、ASP であるためです。NET の自動生成されたキーは HKCU レジストリに保持されます。ユーザー プロファイルが IIS ワーカー プロセスに読み込まれていない場合、アプリケーション プールがリサイクルされると、これらのキーが失われる可能性があります。 このシナリオは、標準の Windows ユーザー アカウントとしてアプリケーション プールを実行している共有ホスティング プロバイダーに影響する可能性があります。
この状況を回避するために、ASP.NET では、HKCU レジストリではなく HKLM レジストリに自動生成されたキーを保持できます。 これは通常、aspnet_regiis ユーティリティを使用して実行されます (「解決策 2a: aspnet_regiis ユーティリティを使用する」セクションの手順を参照してください)。 ただし、このユーティリティを実行したくない管理者の場合は、代わりに次のWindows PowerShell スクリプトを使用できます。
# Provisions the HKLM registry so that the specified user account can persist auto-generated machine keys.
function Provision-AutoGenKeys {
[CmdletBinding()]
param (
[ValidateSet("2.0", "4.0")]
[Parameter(Mandatory = $True)]
[string] $frameworkVersion,
[ValidateSet("32", "64")]
[Parameter(Mandatory = $True)]
[string] $architecture,
[Parameter(Mandatory = $True)]
[string] $upn
)
process {
# We require administrative permissions to continue.
if (-Not (new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Error "This cmdlet requires Administrator permissions."
return
}
# Open HKLM with an appropriate view into the registry
if ($architecture -eq "32") {
$regView = [Microsoft.Win32.RegistryView]::Registry32;
} else {
$regView = [Microsoft.Win32.RegistryView]::Registry64;
}
$baseRegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $regView)
# Open ASP.NET base key
if ($frameworkVersion -eq "2.0") {
$expandedVersion = "2.0.50727.0"
} else {
$expandedVersion = "4.0.30319.0"
}
$aspNetBaseKey = $baseRegKey.OpenSubKey("SOFTWARE\Microsoft\ASP.NET\$expandedVersion", $True)
# Create AutoGenKeys subkey if it doesn't already exist
$autoGenBaseKey = $aspNetBaseKey.OpenSubKey("AutoGenKeys", $True)
if ($autoGenBaseKey -eq $null) {
$autoGenBaseKey = $aspNetBaseKey.CreateSubKey("AutoGenKeys")
}
# Get the SID for the user in question, which will allow us to get his AutoGenKeys subkey
$sid = (New-Object System.Security.Principal.WindowsIdentity($upn)).User.Value
# SYSTEM, ADMINISTRATORS, and the target SID get full access
$regSec = New-Object System.Security.AccessControl.RegistrySecurity
$regSec.SetSecurityDescriptorSddlForm("D:P(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GA;;;$sid)")
$userAutoGenKey = $autoGenBaseKey.OpenSubKey($sid, $True)
if ($userAutoGenKey -eq $null) {
# Subkey didn't exist; create and ACL appropriately
$userAutoGenKey = $autoGenBaseKey.CreateSubKey($sid, [Microsoft.Win32.RegistryKeyPermissionCheck]::Default, $regSec)
} else {
# Subkey existed; make sure ACLs are correct
$userAutoGenKey.SetAccessControl($regSec)
}
}
}
次の例では、ユーザーとして実行されるアプリケーション プールに適切な HKLM レジストリ エントリをプロビジョニングする方法を示します (これは Windows ユーザー アカウントの UPN です) example@contoso.com 。 このアプリケーション プールは、CLR v2.0 (ASP.NET 2.0 または 3.5) を実行している 32 ビット アプリケーション プールです。
PS> Provision-AutoGenKeys -FrameworkVersion 2.0 -Architecture 32 -UPN "example@contoso.com"
代わりに、アプリケーション プールが CLR v4.0 (ASP.NET 4.0 または 4.5) を実行している 64 ビット アプリケーション プールの場合、コマンドは次のようになります。
PS> Provision-AutoGenKeys -FrameworkVersion 4.0 -Architecture 64 -UPN "example@contoso.com"
自動生成されたキーが HKLM に格納されている場合でも、各ユーザー アカウントのシークレット暗号化マテリアルを保持するレジストリ サブキーがアクセス制御リスト (ACL) に追加され、暗号化マテリアルを他のユーザー アカウントで読み取ることができません。
付録 C: 構成ファイル内の <machineKey> 要素の暗号化
サーバー管理者は、 <machineKey> キーマテリアルなどの機密性の高い情報を構成ファイルのプレーンテキスト形式にしたくない場合があります。 この場合、管理者は"保護された構成" と呼ばれる.NET Framework機能を利用することを決定できます。この機能を使用すると、.config ファイルの特定のセクションを暗号化できます。 これらの構成ファイルの内容が公開された場合でも、これらのセクションの内容は秘密のままです。
保護された構成の簡単な概要については、MSDN Web サイトを参照してください。 また、Web.config ファイルの <connectionStrings> および <machineKey> 要素を保護する方法に関するチュートリアルも含まれています。