解決檢視狀態訊息驗證碼 (MAC) 錯誤


什麼是檢視狀態?

檢視狀態是在 ASP.NET 應用程式中的 WebForms (.aspx) 頁面之間來回的資訊。__VIEWSTATE 欄位的 HTML 標記可能如下所示:

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

可能會儲存於 __VIEWSTATE 欄位的項目的其中一例為 Button 控制項的文字。如果使用者按一下按鈕,Button_Click 事件處理常式將可以從檢視狀態欄位擷取 Button 控制項的文字。請參閱 Microsoft Developer Network (MSDN) 網站上的 ASP.NET 檢視狀態概觀 主題以取得詳細的 ASP.NET 檢視狀態概觀。

由於 __VIEWSTATE 欄位包含可在回傳時重新建構頁面的重要資訊,請確認攻擊者無法竄改此欄位。如果攻擊者送出惡意 __VIEWSTATE 承載,攻擊者可能會誘騙應用程式執行在其他狀況下無法執行的動作。

如果要防止此竄改攻擊,訊息驗證碼 (MAC) 會保護 __VIEWSTATE 欄位。ASP.NET 會驗證回傳發生時,與 __VIEWSTATE 承載一併送出的 MAC。Web.config 檔案中的應用程式 元素 (機器翻譯) 會指定用於計算 MAC 的金鑰。由於攻擊者無法猜出 <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。
Client 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 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:Web 應用程式正在伺服陣列中 (多伺服器環境) 中執行

ASP.NET 會自動產生密碼編譯金鑰給每個應用程式,並將金鑰儲存於 HKCU 登錄 Hive 中。如果應用程式設定內沒有明確的 <machineKey> 元素,則會使用此自動產生的金鑰。不過,由於此自動產生的金鑰是在建立金鑰的電腦本機上,這種情形會使伺服器陣列中執行的應用程式產生問題。伺服器陣列中的每個伺服器將會產生各自的本機金鑰,而陣列中的所有伺服器都無法同意要使用哪個金鑰。如此一來,如果一個伺服器產生不同於伺服器取用的 __VIEWSTATE 承載,消費者將會遇到 MAC 驗證失敗的情形。
  • 解決方法 1a:建立明確 <machineKey> 元素

    透過將明確的 <machineKey> 元素新增至應用程式的 Web.config 檔案,開發人員會告知 ASP.NET 不要使用自動產生的密碼編譯金鑰。請參閱附錄 A 以取得有關如何產生 <machineKey> 元素的指示。將此元素新增至 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 執行受管理的應用程式所需的設定。其中一個設定會在登錄 Hive 中建立必要的金鑰,以啟用保留自動產生的機器金鑰。

    首先,您必須判斷網站使用的應用程式集區。這可使用 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 不要使用自動產生的密碼編譯金鑰。請參閱附錄 A 以取得有關如何產生 <machineKey> 元素的指示。


原因 3:應用程式集區是使用 LoadUserProfile=false 所設定

如果應用程式集區使用自訂身分識別執行,IIS 可能尚未載入該身分識別的使用者設定檔。這可能會產生副作用,造成 ASP.NET 無法使用 HKCU 登錄保存自動產生的 <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 登錄 Hive、暫存資料夾以及其他使用者專屬的儲存位置適用於應用程式。不過,這可能會導致工作者處理程序的磁碟或記憶體使用量增加。請參閱 元素 (英文),以取得有關如何啟用此設定的詳細資訊。

原因 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 記錄)。不過,當用戶端送出包含「使用者 A」的 __VIEWSTATE 欄位,這種不相符的情形就會導致作業失敗。
  • 解決方法 4a:確認已正確設定 ViewStateUserKey

    如果您的應用程式使用 ViewStateUserKey 屬性,請確認屬性值在檢視狀態產生時和取用時皆為相同。如果您正在使用目前登入的使用者名稱,請確認使用者仍處於登入狀態,且使用者的身分識別在回傳時並未變更。如果您使用的是目前使用者的工作階段識別項,請確認工作階段並未逾時。

    如果您是在陣列環境中執行,請確認 <machineKey> 元素相符。請參閱附錄 A 以取得如何產生這些元素的指示。

附錄 A:如何產生 <machineKey> 元素

安全性警告

有許多網站只要您按一下按鈕就會產生 <machineKey> 元素。絕對不要使用從這些網站取得的 <machineKey> 元素。由於無法得知這些金鑰是否為安全建立,或是否記錄於某秘密資料庫中。您只應使用您自己建立的 <machineKey> 設定元素。


如果要自行產生 <machineKey> 元素,您可以使用下列 Windows PowerShell (英文) 指令碼:

# 產生 <machineKey> 元素,且可以複製並貼到 Web.config 檔案。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 檔案。<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    }# 使用適當的登錄檢視開啟 HKLMif ($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# SYSTEM、ADMINISTRATORS 和目標 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 登錄項目。這個應用程式集區是執行 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> 元素的教學課程。
注意 :本文屬於「快速發佈」文章,係由 Microsoft 技術支援或組織內部直接建立。 本文所包含的資訊是為了回應新問題而依現況提供。 因此為了迅速對外發佈,文章內容可能含有印刷錯誤,而且可能會在不另行通知的情況下進行修改。 如需其他考量事項,請參閱使用規定
內容

文章識別碼: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
意見反應