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

套用到
.NET Framework 4.5 .NET Framework 3.5.1

什麼是觀測狀態?

檢視狀態是指在 WebForms (.aspx) 頁面間循環傳遞的資訊,這些資訊在 ASP.NET 應用程式中進行。 __VIEWSTATE欄位的 HTML 標記類似以下內容:

<輸入類型=「隱藏」名稱=「__VIEWSTATE」id=「__VIEWSTATE」值=“...” />
一個可能儲存在__VIEWSTATE欄位的項目範例是按鈕控制項的文字。 如果使用者點擊按鈕,Button_Click事件處理器就能從檢視狀態欄位擷取按鈕的文字。 請參閱 MSDN) 網站上Microsoft開發者網絡 (ASP.NET View State 概覽 主題,以獲得更詳細的 ASP.NET 視圖狀態概述。

由於__VIEWSTATE欄位包含重要資訊,用於在postback重建頁面,請確保攻擊者無法篡改此欄位。 若攻擊者提交惡意__VIEWSTATE有效載荷,攻擊者可能誘使應用程式執行原本不會執行的動作。

為防止此類竄改攻擊,__VIEWSTATE欄位由訊息認證碼 (MAC) 保護。 ASP.NET 在回傳時驗證提交的 MAC 地址及 __VIEWSTATE 有效載荷。 用於計算 MAC 的鍵在應用程式的 Web.config 檔案元素中指定。 由於攻擊者無法猜測 machineKey> 元素的內容<,若攻擊者試圖篡改 __VIEWSTATE 有效載荷,攻擊者無法提供有效的 MAC。 ASP.NET 會偵測到沒有提供有效的 MAC,ASP.NET 會拒絕惡意請求。

MAC 驗證錯誤的原因是什麼?

MAC 驗證錯誤的例子如下:

注意

伺服器錯誤顯示在 '/' 應用程式中。

視態MAC驗證失敗。 若此應用程式由網路農場或叢集託管,請確保 <machineKey> 設定指定相同的驗證鍵與驗證演算法。 自動生成無法在叢集中使用。

說明:在執行當前網頁請求時發生了未處理的例外。 請查看堆疊追蹤,以了解錯誤及其在程式碼中起源的更多資訊。

例外細節:System.Web.HttpException:檢視狀態 MAC 驗證失敗。 若此應用程式由網路農場或叢集託管,請確保 <machineKey> 設定指定相同的驗證鍵與驗證演算法。 自動生成無法在叢集中使用。

來源錯誤:[無相關來源線]

來源檔案:...行數: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 驗證失敗。 若此應用程式由網路農場或叢集託管,請確保 <machineKey> 設定指定相同的驗證鍵與驗證演算法。 自動生成無法在叢集中使用。

詳情請參見 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 (布林 includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +7959
System.Web.UI.Page.ProcessRequest (布林 includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +429
System.Web.UI.Page.ProcessRequest () +125
System.Web.UI.Page.ProcessRequestWithNoAssert (HttpContext context) +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 步驟,布林& 完成同步) +140

原因一:網頁應用程式運行在多伺服器 (農場環境中)

ASP.NET 會自動為每個應用程式產生一組密碼金鑰,並將金鑰儲存在 HKCU 的註冊箱蜂巢中。 當應用程式設定中沒有明確 <的 machineKey> 元素時,會使用此自動產生的金鑰。 然而,由於這個自動產生的金鑰是本地於創建該金鑰的電腦,這種情況會對在農場中執行的應用程式造成問題。 農場中的每個伺服器都會產生自己的本地金鑰,且農場內的伺服器不會同意使用哪一把金鑰。 結果是,如果一台伺服器產生的__VIEWSTATE有效載荷被另一台伺服器消耗,消費者將經歷 MAC 驗證失敗。

  • 解決 1a:建立明確 <的機器 Key> 元素

    透過在應用程式的 Web.config 檔案中加入明確的 <machineKey> 元素,開發者告訴 ASP.NET 不要使用自動產生的密碼金鑰。 請參閱 附錄 A 以了解如何產生 <machineKey> 元素。 將此元素加入 Web.config 檔案後,將應用程式重新部署到農場中的每台伺服器。

    注意:部分網頁主機服務,如 Microsoft Azure 網站,會採取措施將每個應用程式自動產生的金鑰同步至其後端伺服器。 這讓未指定明確 <機器鍵> 元素的應用程式能繼續在這些環境中運作,即使該應用程式在農場中執行。 如果您的應用程式運行於第三方主機服務上,請聯絡您的主機供應商,確認此情況是否適用於您。

  • 解決 1b:在負載平衡器中啟用親和力

    如果你的網站有負載平衡器,可以啟用伺服器親和力暫時繞過這個問題。 這有助於確保任何特定用戶端只會與負載平衡器背後的一台實體伺服器互動,使所有加密有效載荷同時由同一伺服器產生並消耗。

    這不應被視為問題的長期解決方案。 即使啟用了伺服器親和性,大多數負載平衡器仍會在原本負載平衡器所連結的伺服器離線時,將用戶端重新導向到另一台實體伺服器。 這會導致新伺服器拒絕 (密碼學有效載荷,如__VIEWSTATE、表單認證工單、MVC、防偽造令牌,以及客戶端目前已有的其他服務) 。

    使用明確 <的機器鍵> 元素並重新部署應用程式,應比啟用伺服器親和性更為可取。

原因二:工作程序使用 IIS 7.0 應用程式池識別碼

Internet Information Services (IIS) 7.0 (Windows Vista,2008 Windows Server) 引入了應用程式池識別碼,這是一種新的隔離機制,有助於提升執行 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 工具來判斷。 在左側的樹狀圖中選擇您的網站,右鍵點擊 「管理 Website」,然後點選 「進階設定」。 出現的對話框會顯示應用程式池名稱。

    進階設定

    要為 ASP.NET 4.0 應用程式池建立適當的登錄檔金鑰,請依照以下步驟進行:

    1. 打開一個行政指令提示字元。

    2. 根據你的應用程式池是 32 位元還是 64 位元,找到合適的目錄:

      • 32-bit application pool: cd /d %windir%\Microsoft.NET\Framework\v4.0.30319
      • 64-bit 應用池: 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-bit application pool: cd /d %windir%\Microsoft.NET\Framework\v2.0.50727
      • 64-bit application pool: 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:建立明確<的機器 關鍵元素>

    透過在應用程式的 Web.config 檔案中加入明確的 <machineKey> 元素,開發者告訴 ASP.NET 不要使用自動產生的密碼金鑰。 請參閱 附錄 A 以了解如何產生 <machineKey> 元素。

原因 3:應用程式池是透過使用 LoadUserProfile=false 來設定的

若應用程式池以自訂身份執行,IIS 可能未載入該身份的使用者設定檔。 這會使 HKCU 登錄檔無法 ASP.NET 持續保存自動產生 <的 machineKey>。 因此,每次應用程式重新啟動時,都會產生新的自動產生金鑰。 更多資訊請參閱 Microsoft 官網的 使用者檔案 區。

  • 解決方案 3a:使用 aspnet_regiis 工具

    這部分的說明與 第2a號決議相同。 更多資訊請參見該章節。

  • 解決 3b:使用明確 <的 machineKey>

    透過在應用程式的 Web.config 檔案中加入明確的 <machineKey> 元素,開發者告訴 ASP.NET 不要使用自動產生的密碼金鑰。 請參閱 附錄 A 以了解如何產生 <machineKey> 元素。

  • 第3c項決議:手動配置所需的HKCU登錄金鑰

    如果你無法執行aspnet_regiis工具,可以使用 Windows PowerShell 腳本在 HKCU 中配置適當的登錄檔金鑰。 詳情請參閱 附錄B

  • 解決 3d:為此應用程式池設定 LoadUserProfile=true

    你也可以在這個應用程式池中啟用載入使用者設定檔。 這讓 HKCU 的登錄檔蜂巢、臨時資料夾及其他使用者專屬儲存位置可供應用程式使用。 然而,這可能會導致工作程序的磁碟或記憶體使用量增加。 請參閱 元素 以了解如何啟用此設定。

原因四:Page.ViewStateUserKey 屬性值不正確

軟體開發者可選擇使用 Page.ViewStateUserKey 屬性,在__VIEWSTATE欄位新增跨站請求偽造保護。 如果你使用 Page.ViewStateUserKey 屬性,通常會將其設定為像是目前使用者的使用者名稱或使用者的會話識別碼等值。 Microsoft Visual Studio 2012 及以後版本中 WebForms 應用程式的專案範本中,包含使用此特性的範例。 欲了解更多資訊,請參閱 MSDN) 網站上 Microsoft 開發者網絡 (Page.ViewStateUserKey 屬性 主題。

若指定 ViewStateUserKey 屬性,其值會在產生時燒錄__VIEWSTATE。 當 __VIEWSTATE 欄位被使用時,伺服器會檢查目前頁面的 ViewStateUserKey 屬性,並與產生 __VIEWSTATE 欄位的值進行驗證。 若數值不符,請求將被視為潛在惡意而被拒絕。

一個與 ViewStateUserKey 相關的失敗例子是客戶端在瀏覽器中開啟了兩個分頁。 用戶端以使用者 A 登入,在第一個分頁中,會呈現一個頁面,並以一個 __VIEWSTATE 呈現,其 ViewStateUserKey 屬性包含「User A」。在第二個分頁,客戶端登出後再以使用者 B 登入。客戶回到第一個分頁並提交表單。 ViewStateUserKey 屬性可能包含「User B」 (,因為客戶端的認證 cookie) 顯示這個名稱。 然而,客戶提交的__VIEWSTATE欄位中包含「使用者 A」。這種不匹配會導致失效。

  • 解決 4a:確認 ViewStateUserKey 是否正確設定

    如果你的應用程式使用 ViewStateUserKey 屬性,請確認該屬性在產生檢視狀態和被使用時的值是否相同。 如果你使用的是目前已登入使用者的使用者名稱,請確認該使用者仍在登入狀態,且回傳時其身份未曾更改。 如果你使用目前使用者的會話識別碼,請確認會話沒有逾時。

    如果你是在農場環境中運行,請確保 machineKey> 元素相<符。 請參閱 附錄A ,了解如何產生這些元素。

          

附錄 A:如何產生<機器鍵元素>

注意

安全性警告

有許多網站只需點擊按鈕就能為你產生 <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。 此應用池為 32 位元應用池,運行 CLR v2.0 (ASP.NET 2.0 或 3.5) 。


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> 元素。