什麼是檢視狀態?
檢視狀態是在 ASP.NET 應用程式中的 WebForms (.aspx) 頁面之間往返的資訊。 __VIEWSTATE欄位的 HTML 標記類似下列:
<輸入類型=“隱藏” name=“__VIEWSTATE” id=“__VIEWSTATE” value=“...” />
可能儲存在 __VIEWSTATE 欄位中的項目範例之一是 Button 控制項的文字。 如果使用者按一下按鈕,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> 設定指定相同的 validationKey 和驗證演算法。 AutoGenerate 無法在叢集中使用。
描述:執行目前 Web 要求期間發生未處理的例外狀況。 請檢閱堆疊追蹤,以取得有關錯誤的詳細資訊,以及錯誤的來源位置。
例外狀況詳細資料: System.Web.HttpException: 檢視狀態 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)
視圖狀態:...]
[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 (字串 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 (布林值 includeStagesBeforeAsyncPoint,布林值 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 步驟,布林值& completedSynchronously) +140
原因 1:Web 應用程式在陣列 (多伺服器環境中執行)
ASP.NET 會自動為每個應用程式產生密碼編譯金鑰,並將金鑰儲存在 HKCU 登錄配置單元中。 如果應用程式的設定中沒有明確的 <machineKey> 元素,則使用此自動產生的金鑰。 不過,由於此自動產生的金鑰是建立金鑰的電腦本機,因此這種情況會對在陣列中執行的應用程式造成問題。 陣列中的每部伺服器都會產生自己的本機金鑰,而且陣列中的任何伺服器都不會就使用哪個金鑰達成一致。 結果是,如果一部伺服器產生由另一部伺服器取用的 __VIEWSTATE 承載,消費者將會遇到 MAC 驗證失敗。
解決方案 1a:建立明確的 <machineKey> 元素
透過將明確的 <machineKey> 元素新增至應用程式的 Web.config 檔案,開發人員會告知 ASP.NET 不要使用自動產生的密碼編譯金鑰。 請參閱 附錄 A ,以取得有關如何產生 <machineKey> 元素的指示。 將此元素新增至 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 應用程式集區搭建適當的登錄機碼,請遵循下列步驟:
開啟系統管理命令提示字元。
根據您的應用程式集區是 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 不要使用自動產生的密碼編譯金鑰。 請參閱 附錄 A ,以取得有關如何產生 <machineKey> 元素的指示。
原因 3:應用程式集區是使用 LoadUserProfile=false 設定的
如果應用程式集區是使用自訂身分識別執行,IIS 可能尚未載入身分識別的使用者設定檔。 這樣做的副作用是讓 HKCU 登錄無法供 ASP.NET 保存自動產生 <的 machineKey>。 因此,每次應用程式重新啟動時,都會建立新的自動產生金鑰。 如需詳細資訊,請參閱 Microsoft 網站上的 「使用者設定檔」 章節。
解決方案 3a:使用 aspnet_regiis 公用程式
此操作說明與 解決方案 2a 相同。 如需詳細資訊,請參閱該章節。
解決方案 3b: 使用明確的 <machineKey>
透過將明確的 <machineKey> 元素新增至應用程式的 Web.config 檔案,開發人員會告知 ASP.NET 不要使用自動產生的密碼編譯金鑰。 請參閱 附錄 A ,以取得有關如何產生 <machineKey> 元素的指示。
解決方案 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 登入,在第一個索引標籤中,頁面會顯示具有 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.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> 元素的教學課程。