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

应用对象
.NET Framework 4.5 .NET Framework 3.5.1

什么是视图状态?

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

<输入类型=“隐藏” name=“__VIEWSTATE” id=“__VIEWSTATE” value=“...” />
可能存储在 __VIEWSTATE 字段中的项的一个示例是按钮控件的文本。 如果用户单击该按钮,Button_Click 事件处理程序将能够从视图状态字段中提取按钮的文本。 有关 ASP.NET 视图状态的更详细概述,请参阅 Microsoft Developer Network (MSDN) 网站上的“ 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> 配置指定相同的验证密钥和验证算法。 AutoGenerate 不能用于群集。

说明:在执行当前 Web 请求期间发生未处理的异常。 有关错误及其在代码中的位置的详细信息,请查看堆栈跟踪。

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

源错误:[无相关源行]

源文件: ...行:0

堆栈跟踪:

[ViewStateException: 视图状态无效。
客户端 IP: ::1
端口: 40653
Referer:http://localhost:40643/MyPage.aspx
路径:/MyPage.aspx
用户代理: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 (异常内部,字符串 persistedState,字符串 errorPageMessage,布尔值 macValidationError) +190
System.Web.UI.ViewStateException.ThrowMacValidationError (异常内部,字符串 persistedState) +46
System.Web.UI.ObjectStateFormatter.Deserialize (String inputString, 用途) +861
System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter2.Deserialize (字符串 serializedState,用途) +51
System.Web.UI.Util.DeserializeWithAssert (IStateFormatter2 格式化程序, 字符串 serializedState, 用途) +67
System.Web.UI.HiddenFieldPageStatePersister.Load () +444
System.Web.UI.Page.LoadPageStateFromPersistenceMedium () +368
System.Web.UI.Page.LoadAllState () +109
System.Web.UI.Page.ProcessRequestMain (布尔值 includeStagesBeforeAsyncPoint, 布尔值 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 步骤,布尔值& 已完成同步) +140

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

ASP.NET 自动为每个应用程序生成一个加密密钥,并将该密钥存储在 HKCU 注册表配置单元中。 如果应用程序配置中没有显式 <machineKey> 元素,将使用此自动生成的密钥。 但是,由于此自动生成的密钥是创建该密钥的计算机的本地密钥,因此这种情况会导致在场中运行的应用程序出现问题。 场中的每个服务器都会生成自己的本地密钥,场中的任何服务器都不会就使用哪个密钥达成一致。 结果是,如果一个服务器生成另一个服务器使用的 __VIEWSTATE 有效负载,则使用者将遇到 MAC 验证失败。

  • 解决方案 1a:创建显 <式 machineKey> 元素

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

    注意 某些 Web 托管服务(如 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”

    注意:系统服务 APPHOSTSVC 和 WAS 可能必须运行才能使 aspnet_regiis 实用工具正确解析 IIS APPPOOL\* 名称。

  • 解决方案 2b:创建显 <式 machineKey> 元素

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

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

如果应用程序池正在使用自定义标识运行,则 IIS 可能尚未加载该标识的用户配置文件。 这样做的副作用是,HKCU 注册表无法让 ASP.NET 持久保存自动生成 <的 machineKey>。 因此,每次应用程序重新启动时,都会创建一个新的自动生成密钥。 有关详细信息,请参阅 Microsoft 网站上的用户 配置文件 部分。

  • 解决方案 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) 网站上的 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 脚本:


# 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 启用将自动生成的密钥保存在 HKLM 注册表中,而不是 HKCU 注册表中。 这通常通过使用 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 注册表项, example@contoso.com (这是 Windows 用户帐户) 的 UPN。 此应用程序池是一个运行 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.config 文件的 connectionStrings> 和 <machineKey> 元素的教程。