什么是视图状态?
视图状态是在 ASP.NET 应用程序中的 WebForms (.aspx) 页之间舍入的信息。 __VIEWSTATE字段的 HTML 标记如下所示:
<input type=“hidden” name=“__VIEWSTATE” id=“__VIEWSTATE” value=“...”/>
可能存储在“__VIEWSTATE”字段中的项的一个示例是 Button 控件的文本。 如果用户单击该按钮,Button_Click事件处理程序将能够从视图状态字段中提取 Button 的文本。 有关 ASP.NET 视图状态 的更详细概述,请参阅Microsoft开发人员网络 (MSDN) 网站上的 ASP.NET 视图状态概述主题。
由于 __VIEWSTATE 字段包含用于在回发时重建页面的重要信息,因此请确保攻击者无法篡改此字段。 如果攻击者提交了恶意__VIEWSTATE有效负载,攻击者可能会诱使应用程序执行它本来不会执行的操作。
为了防止这种篡改攻击,__VIEWSTATE字段受消息身份验证代码 (MAC) 的保护。 发生回发时,ASP.NET 验证与__VIEWSTATE有效负载一起提交的 MAC。 用于计算 MAC 的键在 Web.config 文件中的应用程序 元素 中指定。 由于攻击者无法猜测 machineKey> 元素的内容<,因此如果攻击者尝试篡改__VIEWSTATE有效负载,则攻击者无法提供有效的 MAC。 ASP.NET 将检测到未提供有效的 MAC,并且 ASP.NET 将拒绝恶意请求。
导致 MAC 验证错误的原因是什么?
MAC 验证错误将类似于以下示例:
注意
“/”应用程序中的服务器错误。
viewstate MAC 验证失败。 如果此应用程序由 Web 场或群集托管,请确保 <machineKey> 配置指定相同的 validationKey 和验证算法。 不能在群集中使用 AutoGenerate。
说明:执行当前 Web 请求期间发生未经处理的异常。 请查看堆栈跟踪,详细了解错误及其起源于代码的位置。
异常详细信息:System.Web.HttpException:viewstate MAC 验证失败。 如果此应用程序由 Web 场或群集托管,请确保 <machineKey> 配置指定相同的 validationKey 和验证算法。 不能在群集中使用 AutoGenerate。
源错误:[无相关源行]
源文件:...行:0
堆栈跟踪:
[ViewStateException:viewstate 无效。
客户端 IP:::1
端口:40653
引用:http://localhost:40643/MyPage.aspx
路径:/MyPage.aspx
用户代理:Mozilla/5.0 (兼容;MSIE 10.0;Windows NT 6.2;WOW64;Trident/6.0)
ViewState: ...]
[HttpException (0x80004005) :viewstate 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 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 上下文) +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 文件后,将应用程序重新部署到场中的每个服务器。
注意 某些 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 运行时存储其自动生成 <的计算机密钥> 的位置。 结果是,重置应用程序池时,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 实用工具来确定。 在左侧树视图中选择站点,右键单击“ 管理 Web”,然后单击“ 高级设置”。 显示的对话框将显示应用程序池名称。
若要为 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”注意 系统服务 APPHOSTSVC 和 WAS 可能必须运行,aspnet_regiis实用工具才能正确解析 IIS APPPOOL\* 名称。
解决方法 2b:创建显式 <machineKey> 元素
通过将显式 <machineKey> 元素添加到应用程序的 Web.config 文件,开发人员会告知 ASP.NET 不要使用自动生成的加密密钥。 有关如何生成 <machineKey> 元素的说明,请参阅附录 A。
原因 3:应用程序池是使用 LoadUserProfile=false 配置的
如果应用程序池使用自定义标识运行,则 IIS 可能尚未加载标识的用户配置文件。 这会产生一个副作用,即 ASP.NET 无法使用 HKCU 注册表来保留自动生成 <的计算机密钥>。 因此,每次应用程序重新启动时,都会创建新的自动生成的密钥。 有关详细信息,请参阅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开发人员网络 (MSDN) 网站上的 Page.ViewStateUserKey 属性 主题。
如果指定了 ViewStateUserKey 属性,则其值会在生成时刻录到__VIEWSTATE。 使用__VIEWSTATE字段时,服务器将检查当前页的 ViewStateUserKey 属性,并针对用于生成__VIEWSTATE字段的值对其进行验证。 如果值不匹配,请求将被拒绝,因为可能是恶意请求。
与 ViewStateUserKey 相关的失败示例是一个在浏览器中打开了两个选项卡的客户端。 客户端以用户 A 身份登录,在第一个选项卡中,使用 ViewStateUserKey 属性包含“用户 A”的__VIEWSTATE呈现页面。第二个选项卡中,客户端注销,然后以用户 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。自动生成的密钥将保留在 HKCU 注册表中,如果用户配置文件尚未加载到 IIS 工作进程中,然后应用程序池回收,则这些密钥可能会丢失。 此方案可能会影响作为标准 Windows 用户帐户运行应用程序池的共享托管提供程序。
若要解决此问题,ASP.NET 允许在 HKLM 注册表而不是 HKCU 注册表中保留自动生成的密钥。 这通常通过使用 aspnet_regiis 实用工具来执行, (请参阅) “解决方法 2aa:使用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> 元素的教程。