解决视图状态消息验证代码 (MAC) 错误


什么是视图状态?

视图状态是指在 ASP.NET 应用程序的 WebForms (.aspx) 页面中往返的信息。__VIEWSTATE 字段的 HTML 标记如下所示:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..."/>

例如,可以将按钮控件的文本存储在 __VIEWSTATE 字段中。如果用户单击该按钮,则 Button_Click 事件处理程序将能够从视图状态字段中提取该按钮的文本。有关 ASP.NET 视图状态的更详细概述,请参阅 Microsoft Developer Network (MSDN) 网站上的 ASP.NET 视图状态概述主题。

由于 __VIEWSTATE 字段包含用于在回发时重建页面的重要信息,因此请确保攻击者无法篡改此字段。如果攻击者提交恶意的 __VIEWSTATE 负载,则攻击者可能会诱骗应用程序执行本不会执行的操作。

若要避免此类篡改攻击,可以使用消息验证代码 (MAC) 来保护 __VIEWSTATE 字段。回发时,ASP.NET 会验证与 __VIEWSTATE 负载一起提交的 MAC。在 Web.config 文件中,应用程序 元素中指定了用于计算 MAC 的密钥。由于攻击者无法猜测 <machineKey> 元素的内容,因此也就无法在试图篡改 __VIEWSTATE 负载时提供有效的 MAC。ASP.NET 会检测到有效的 MAC 没有提供,并拒绝此恶意请求。

导致 MAC 验证错误的原因?

MAC 验证错误的内容类似于以下示例:

Server Error in '/' Application.

视图状态 MAC 的验证失败。如果此应用程序是由 Web 场或群集托管,请确保 <machineKey> 配置指定的 validationKey 和验证算法相同。无法在群集中使用自动生成功能。

描述:执行当前的 Web 请求过程中出现未经处理的异常。请查看堆栈跟踪,详细了解该错误以及代码中的源错误。

异常详细信息:System.Web.HttpException:视图状态 MAC 的验证失败。如果此应用程序是由 Web 场或群集托管,请确保 <machineKey> 配置指定的 validationKey 和验证算法相同。无法在群集中使用自动生成功能。

源错误:[没有相关的源错误代码]

源文件:... 行:0

堆栈跟踪:

[视图状态异常:视图状态无效。
客户端 IP:::1
端口:40653
引用方:http://localhost:40643/MyPage.aspx
路径:/MyPage.aspx
用户代理:Mozilla/5.0(兼容;MSIE 10.0;Windows NT 6.2;WOW64;Trident/6.0)
视图状态:...]

[HttpException (0x80004005):视图状态 MAC 的验证失败。如果此应用程序是由 Web 场或群集托管,请确保 <machineKey> 配置指定的 validationKey 和验证算法相同。无法在群集中使用自动生成功能。

有关详细信息,请访问 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 formatter, 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 context) +234
ASP.mypage_aspx.ProcessRequest(HttpContext context) in ...:0
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +1300
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +140

原因 1:网络应用程序在场(多服务器环境)中运行

ASP.NET 为每个应用程序自动生成加密密钥,并将密钥存储在 HKCU 注册表配置单元中。如果应用程序的配置中没有明确指定 <machineKey> 元素,则会使用该自动生成的密钥。不过,由于此自动生成的密钥是创建了该密钥的计算机的本地密钥,因此这样会导致在场中运行的应用程序出现问题。场中的每个服务器都会生成自己的本地密钥,并且都不会就使用哪一个密钥达成一致。结果,如果一个服务器生成的 __VIEWSTATE 负载被其他服务器使用,那么使用者会遇到 MAC 验证失败问题。
  • 解决方案 1a:创建明确的 <machineKey> 元素

    通过向应用程序 Web.config 文件添加明确的 <machineKey> 元素,开发人员可以指示 ASP.NET 不要使用自动生成的加密密钥。有关如何生成 <machineKey> 元素的说明,请参阅附录 A。在向 Web.config 文件中添加此元素后,将应用程序重新部署到场中的每个服务器。

    注意 一些网络托管服务(如 Microsoft Azure 网站)会采取措施在后端服务器之间同步每个应用程序的自动生成的密钥。这样,没有明确指定 <machineKey> 元素的应用程序可以继续在此类环境中运行,即使该应用程序是在场中运行。如果应用程序是在第三方托管服务上运行,请与您的托管提供者联系,确定这种情况对您是否适用。

  • 解决方案 1b:在负载平衡器中启用关联

    如果您的网站是在负载平衡器的幕后运行,您可以通过启用服务器关联来暂时解决此问题。这将有助于确保任何指定客户端只与负载平衡器后的一个物理服务器互动,这样所有加密负载都将由同一个服务器生成和使用。

    不得将此方法看作该问题的长期解决方案。即使启用了服务器关联,如果负载平衡器关联至的原始服务器为离线状态,则大多数负载平衡器也还会将客户端重定向到不同的物理服务器。这会导致新服务器拒绝客户端当前的加密负载(如 __VIEWSTATE、表单身份验证请求、MVC 防伪标记和其他服务)。

    与启用服务器关联相比,应优先考虑使用以下方法:使用明确的 <machineKey> 元素并重新部署应用程序。

原因 2:工作进程使用 IIS 7.0 应用程序池标识

Internet Information Services (IIS) 7.0(Windows Vista、Windows Server 2008)引入了应用程序池标识,这是一种新的隔离机制,有助于提高运行 ASP.NET 应用程序的服务器的安全性。不过,根据应用程序池标识运行的网站无权访问 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 互动,以执行运行托管应用程序所需的配置。其中一项配置就可以在注册表配置单元中创建必要的密钥,以暂留自动生成的计算机密钥。

    首先,您需要确定网站使用的是哪个应用程序池。为此,您可以使用 IIS 包含的 inetmgr 实用工具进行确定。从左侧的树视图中选择您的网站,右键单击“管理网站”,然后单击“高级设置”。所出现的对话框中显示应用程序池名称。

    高级设置

    若要创建适用于 ASP.NET 4.0 应用程序池的注册表项,请按照下列步骤操作:
    1. 打开管理命令提示符。
    2. 找到相应的目录,具体取决于您的应用程序池是 32 位还是 64 位:
      • 32 位应用程序池:cd /d %windir%\Microsoft.NET\Framework\v4.0.30319
      • 64 位应用程序池:cd /d %windir%\Microsoft.NET\Framework64\v4.0.30319

    3. 移至目录,键入下列命令,然后按 Enter 键:

      aspnet_regiis -ga "IIS APPPOOL\app-pool-name"

    如果应用程序池是 ASP.NET 2.0 或 3.5 应用程序池,请按照下列步骤操作:
    1. 打开管理命令提示符。
    2. 找到相应的目录,具体取决于您的应用程序池是 32 位还是 64 位:
      • 32 位应用程序池:cd /d %windir%\Microsoft.NET\Framework\v2.0.50727
      • 64 位应用程序池:cd /d %windir%\Microsoft.NET\Framework64\v2.0.50727

    3. 移至目录,键入下列命令,然后按 Enter 键:

      aspnet_regiis -ga "IIS APPPOOL\app-pool-name"

    例如,如果应用程序池的名称为“My App Pool”(如上一图像所示),请运行以下命令:
     
    aspnet_regiis -ga "IIS APPPOOL\My App Pool"


    注意 必须针对 aspnet_regiis 实用工具运行系统服务 APPHOSTSVC 和 WAS 才能解析相应的 IIS APPPOOL\* 名称。

  • 解决方案 2b:创建明确的 <machineKey> 元素

    通过向应用程序 Web.config 文件添加明确的 <machineKey> 元素,开发人员可以指示 ASP.NET 不要使用自动生成的加密密钥。有关如何生成 <machineKey> 元素的说明,请参阅附录 A


原因 3:使用 LoadUserProfile=false 配置了应用程序池

如果应用程序池标识为自定义,那么 IIS 可能没有针对该标识加载用户配置文件。这会导致以下意外后果:ASP.NET 无法使用 HKCU 注册表暂留自动生成的 <machineKey>。结果,每当应用程序重启时,都会新建一个自动生成的密钥。有关详细信息,请参阅 Microsoft 网站上的用户配置文件部分。
  • 解决方案 3a:使用 aspnet_regiis 实用工具

    此解决方案的说明与解决方案 2a 相同。有关详细信息,请参阅相关部分。

  • 解决方案 3b:使用明确的 <machineKey>

    通过向应用程序 Web.config 文件添加明确的 <machineKey> 元素,开发人员可以指示 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) 网站上的 Page.ViewStateUserKey 属性主题。

如果 ViewStateUserKey 属性已指定,其值会在生成时刻录到 __VIEWSTATE 中。在使用 __VIEWSTATE 字段时,服务器会检查当前页面的 ViewStateUserKey 属性,并根据用于生成 __VIEWSTATE 字段的值来验证属性值。如果值不匹配,则服务器会将请求作为可能的恶意请求进行拒绝。

例如,客户端在浏览器中打开了两个选项卡,导致了与 ViewStateUserKey 相关的故障。客户端以用户 A 的身份进行登录,在第一个选项卡中,呈现的页面使用的 __VIEWSTATE 的 ViewStateUserKey 属性中包含“用户 A”。在第二个选项卡中,该客户端退出并以用户 B 的身份重新登录。然后,客户端返回到第一个选项卡并提交表单。ViewStateUserKey 属性可能包含“用户 B”(因为这是由客户端的身份验证 Cookie 所指示)。不过,客户端提交的 __VIEWSTATE 字段中包含“用户 A”。这种不匹配导致了故障的发生。
  • 解决方案 4a:验证 ViewStateUserKey 的设置是否正确

    如果应用程序使用 ViewStateUserKey 属性,请验证在视图状态生成和使用时该属性值是否均相同。如果您使用的是当前登录用户的用户名,请确保该用户仍处于登录状态,且该用户的身份在回发时未改变。如果您使用的是当前用户的会话标识符,请确保该会话未超时。

    如果是在场环境中运行,请确保 <machineKey> 元素匹配。有关如何生成这些元素的说明,请参阅附录 A

附录 A:如何生成 <machineKey> 元素

安全警告

只需单击一下按钮,您就可以通过许多网站生成 <machineKey> 元素。切勿使用通过这些网站获得的 <machineKey> 元素。这些密钥是否安全创建或者是否记录到秘密数据库,都无从而知。必须使用您自己创建的 <machineKey> 配置元素。


若要自己生成 <machineKey> 元素,可以使用以下 Windows PowerShell 脚本:

# 生成一个可复制并粘贴到 Web.config 文件中的 <machineKey> 元素。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.StringBuilderforeach ($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 文件中。只有添加到位于应用程序根目录的 Web.config 文件中,<machineKey> 元素才有效;添加到子文件夹级别的 Web.config 文件中,该元素则无效。

<configuration><system.web><machineKey ... /></system.web></configuration>

有关支持算法的完整列表,请从 Windows PowerShell 提示符运行 help Generate-MachineKey

附录 B:将注册表配置为暂留自动生成的密钥

默认情况下,由于 ASP.NET 自动生成的密钥暂留在 HKCU 注册表中,如果 IIS 工作进程没有加载用户配置文件,那么这些密钥会丢失,然后应用程序池会进行回收。这种情况会对将应用程序池作为标准的 Windows 用户帐户进行运行的共享托管提供者产生影响。

为了解决此问题,ASP.NET 会启用在 HKLM 注册表中暂留自动生成的密钥,而不是在 HKCU 注册表中。通常可使用 aspnet_regiis 实用工具执行此操作(请参阅“解决方案 2a:使用 aspnet_regiis 实用工具”部分中的说明)。然而,对于不想运行此实用工具的管理员来说,可以改用以下 Windows PowerShell 脚本:

# 配置 HKLM 注册表,以便指定的用户帐户可以暂留自动生成的计算机密钥。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 {# 需要拥有管理权限才能继续。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    }# 打开并查看相应的 HKLM 注册表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)# 打开 ASP.NET 基本密钥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)# 创建 AutoGenKeys 子项(如果尚不存在)$autoGenBaseKey = $aspNetBaseKey.OpenSubKey("AutoGenKeys", $True)if ($autoGenBaseKey -eq $null) {$autoGenBaseKey = $aspNetBaseKey.CreateSubKey("AutoGenKeys")    }# 获取相关用户的 SID,可允许我们获取该用户的 AutoGenKeys 子项$sid = (New-Object System.Security.Principal.WindowsIdentity($upn)).User.Value# 系统、管理员和目标 SID 获取完全访问权限$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) {# 子项不存在;创建子项并执行相应的 ACL 操作$userAutoGenKey = $autoGenBaseKey.CreateSubKey($sid, [Microsoft.Win32.RegistryKeyPermissionCheck]::Default, $regSec)} else {# 子项已存在;确保 ACL 正确无误$userAutoGenKey.SetAccessControl($regSec)    }  }}

以下示例展示了如何为作为用户 example@contoso.com(这是 Windows 用户帐户的 UPN)运行的应用程序池设置相应的 HKLM 注册表项。此应用程序池为 32 位应用程序池,运行 CLR v2.0(ASP.NET 2.0 或 3.5)。

PS> Provision-AutoGenKeys -FrameworkVersion 2.0 -Architecture 32 -UPN "example@contoso.com"

如果应用程序池是 64 位应用程序池,运行 CLR v4.0(ASP.NET 4.0 或 4.5),则命令如下所示:

PS> Provision-AutoGenKeys -FrameworkVersion 4.0 -Architecture 64 -UPN "example@contoso.com"

即使自动生成的密钥存储在 HKLM 中,用于保留每个用户帐户的加密材料的注册表子项也会添加到访问控制列表 (ACL) 中,以免其他用户帐户读取加密资料。

附录 C:在配置文件中加密 <machineKey> 元素

服务器管理员不希望高度敏感信息(如 <machineKey> 密钥资料)在配置文件中为纯文本形式。如果是这样,管理员可以决定利用称为“受保护的配置”的 .NET Framework 功能。使用该功能,您可以加密 .config 文件中的特定部分。即使这些配置文件的内容遭到披露,这些加密部分的内容也仍将会处于保密状态。

您可以访问 MSDN 网站,了解有关受保护的配置的概述。其中还包含一个有关如何保护 Web.config 文件的 <connectionStrings> 和 <machineKey> 元素的教程。
注意:本篇“快速发布”文章是从 Microsoft 支持组织直接创建的。 文中包含的信息按原样提供,用于响应紧急问题。 由于发布仓促,材料可能包含印刷错误,并且可能随时修订,恕不另行通知。 有关其他注意事项,请参阅使用条款
属性

文章 ID:2915218 - 上次审阅时间:06/20/2014 15:18:00 - 修订版本: 2.0

Microsoft .NET Framework 4.5.1 Release Candidate, Microsoft .NET Framework 4.5.1, Microsoft .NET Framework 4.5, Microsoft .NET Framework 4.0, Microsoft .NET Framework 3.5.1, Microsoft .NET Framework 3.5, Microsoft .NET Framework 2.0 Service Pack 2, Microsoft .NET Framework 1.1 Service Pack 1

  • kbsecvulnerability kbsecurity kbsecbulletin kbfix kbexpertiseinter kbbug atdownload KB2915218
反馈