What is view state?
View state is information that is round-tripped between WebForms (.aspx) pages in an ASP.NET application. The HTML markup for the __VIEWSTATE field resembles the following:<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..." />
One example of an item that might be stored in the __VIEWSTATE field is the text of a Button control. If a user clicks the button, the Button_Click event handler will be able to extract the Button's text from the view state field. See the ASP.NET View State Overview topic on the Microsoft Developer Network (MSDN) website for a much more detailed overview of the ASP.NET view state.
Because the __VIEWSTATE field contains important information that is used to reconstruct the page on postback, make sure that an attacker cannot to tamper with this field. If an attacker submitted a malicious __VIEWSTATE payload, the attacker could potentially trick the application into performing an action that it otherwise would not have performed.
To prevent this kind of tampering attack, the __VIEWSTATE field is protected by a message authentication code (MAC). ASP.NET validates the MAC that is submitted together with the __VIEWSTATE payload when a postback occurs. The key that is used to calculate the MAC is specified in the application's element in the Web.config file. Because the attacker cannot guess the contents of the <machineKey> element, the attacker cannot provide a valid MAC if the attacker tries to tamper with the __VIEWSTATE payload. ASP.NET will detect that a valid MAC hasn't been provided, and ASP.NET will reject the malicious request.
What causes MAC validation errors?
A MAC validation error will resemble the following example:Server Error in '/' Application.
Validation of viewstate MAC failed. If this application is hosted by a web farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
Source Error: [No relevant source lines]
Source File: ... Line: 0
Stack Trace:
[ViewStateException: Invalid viewstate.
Client IP: ::1
Port: 40653
Referer: http://localhost:40643/MyPage.aspx
Path: /MyPage.aspx
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
ViewState: ...]
[HttpException (0x80004005): Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
See http://go.microsoft.com/fwlink/?LinkID=314055 for more information.]
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
Validation of viewstate MAC failed. If this application is hosted by a web farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
Source Error: [No relevant source lines]
Source File: ... Line: 0
Stack Trace:
[ViewStateException: Invalid viewstate.
Client IP: ::1
Port: 40653
Referer: http://localhost:40643/MyPage.aspx
Path: /MyPage.aspx
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
ViewState: ...]
[HttpException (0x80004005): Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.
See http://go.microsoft.com/fwlink/?LinkID=314055 for more information.]
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
Cause 1: The web application is running in a farm (multi-server environment)
ASP.NET automatically generates a cryptographic key for each application and stores the key in the HKCU registry hive. This auto-generated key is used if there is no explicit <machineKey> element in the application’s configuration. However, because this auto-generated key is local to the computer that created the key, this scenario causes a problem for applications that run in a farm. Each server in the farm will generate its own local key, and none of the servers in the farm will agree on which key to use. The result is that, if one server generates a __VIEWSTATE payload that a different server consumes, the consumer will experience a MAC validation failure.- Resolution 1a: Create an explicit <machineKey> element
By adding an explicit <machineKey> element to the application's Web.config file, the developer tells ASP.NET not to use the auto-generated cryptographic key. See Appendix A for instructions on how to generate a <machineKey> element. After this element is added to the Web.config file, redeploy the application to each server in the farm.
Note Some web hosting services, such as Microsoft Azure websites, take steps to synchronize each application's auto-generated key across their back-end servers. This lets applications that have not specified an explicit <machineKey> element to continue working in these environments, even if the application is running in a farm. If your application is running on a third-party hosting service, please contact your hosting provider to determine whether this situation applies to you.
- Resolution 1b: Enable affinity in the load balancer
If your sites are operating behind a load balancer, you can enable server affinity to temporarily work around the issue. This helps ensure that any given client only interacts with one physical server behind the load balancer so that all cryptographic payloads will be both generated by and consumed by the same server.
This should not be considered a long-term solution to the problem. Even when server affinity is enabled, most load balancers will redirect the client to a different physical server if the original server to which the load balancers were affinitized goes offline. This causes the new server to reject cryptographic payloads (such as __VIEWSTATE, forms authentication tickets, MVCs anti-forgery tokens, and other services) that the client currently has.
Using an explicit <machineKey> element and redeploying the application should be preferred over enabling server affinity.
Cause 2: The worker process uses the IIS 7.0 application pool identity
Internet Information Services (IIS) 7.0 (Windows Vista, Windows Server 2008) introduced application pool identity, a new isolation mechanism that helps provide increased security for servers that run ASP.NET applications. However, sites that are running under the application pool identity do not have access to the HKCU registry. This is where the ASP.NET runtime stores its auto-generated <machineKey> keys. The result is that ASP.NET cannot persist the auto-generated key when the application pool is reset. Therefore, every time w3wp.exe is reset, a new temporary key is generated.Note This is not an issue in IIS 7.5 (Windows 7, Windows Server 2008 R2) and later versions. On these versions of IIS, ASP.NET can persist its auto-generated keys in a different location that survives application pool resets.
- Resolution 2a: Use the aspnet_regiis utility
ASP.NET installations contain a utility, aspnet_regiis.exe. This utility lets ASP.NET interface with IIS to perform the configurations that are required to run a managed application. One of these configurations creates the necessary keys in the registry hive to enable persistence of auto-generated machine keys.
First, you have to determine which application pool your site is using. This can be determined by using the inetmgr utility that is included with IIS. Select your site in the tree view at the left, right-click Manage Website, and then click Advanced Settings. The dialog box that appears will show the application pool name.
To scaffold the appropriate registry keys for an ASP.NET 4.0 application pool, follow these steps:- Open an administrative command prompt.
- Locate the appropriate directory, depending on whether your application pool is 32-bit or 64-bit:
- 32-bit application pool: cd /d %windir%\Microsoft.NET\Framework\v4.0.30319
- 64-bit application pool: cd /d %windir%\Microsoft.NET\Framework64\v4.0.30319
- Move to the directory, type the following command, and then press Enter:
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
If the application pool is an ASP.NET 2.0 or 3.5 application pool, follow these steps:- Open an administrative command prompt.
- Locate the appropriate directory, depending on whether your application pool is 32-bit or 64-bit:
- 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
- Move to the directory, type the following command, and then press Enter:
aspnet_regiis -ga "IIS APPPOOL\app-pool-name"
For example, if your application pool is named My App Pool (as in the previous image), run the following command:
aspnet_regiis -ga "IIS APPPOOL\My App Pool"
Note The system services APPHOSTSVC and WAS may have to be running for the aspnet_regiis utility to resolve IIS APPPOOL\* names appropriately.
- Resolution 2b: Create an explicit <machineKey> element
By adding an explicit <machineKey> element to the application's Web.config file, the developer tells ASP.NET not to use the auto-generated cryptographic key. See Appendix A for instructions on how to generate a <machineKey> element.
Cause 3: The application pool is configured by using LoadUserProfile=false
If the application pool is running with a custom identity, IIS may not have loaded the user profile for the identity. This has the side effect of making the HKCU registry unavailable for ASP.NET to persist the auto-generated <machineKey>. Therefore, a new auto-generated key will be created every time that the application restarts. See the User Profile section on the Microsoft website for more information.- Resolution 3a: Use the aspnet_regiis utility
The instructions for this are the same as Resolution 2a. See that section for more information.
- Resolution 3b: Use an explicit <machineKey>
By adding an explicit <machineKey> element to the application's Web.config file, the developer tells ASP.NET not to use the auto-generated cryptographic key. See Appendix A for instructions on how to generate a <machineKey> element.
- Resolution 3c: Provision the required HKCU registry keys manually
If you cannot run the aspnet_regiis utility, you can use a Windows PowerShell script to provision the appropriate registry keys in HKCU. See Appendix B for more information.
- Resolution 3d: Set LoadUserProfile=true for this application pool
You can also enable loading the user profile inside this application pool. This makes the HKCU registry hive, temporary folder, and other user-specific storage locations available to the application. However, this may cause increased disk or memory usage for the worker process. See the element for more information about how to enable this setting.
Cause 4: The Page.ViewStateUserKey property has an incorrect value
Software developers can decide to use the Page.ViewStateUserKey property to add cross-site request forgery protection to the __VIEWSTATE field. If you use the Page.ViewStateUserKey property, it is typically set to a value such as the current user’s username or the user's session identifier. The project templates for WebForms applications in Microsoft Visual Studio 2012 and later versions contain samples that use this property. See the Page.ViewStateUserKey Property topic on the Microsoft Developer Network (MSDN) website for more information.If the ViewStateUserKey property is specified, its value is burned into __VIEWSTATE at generation time. When the __VIEWSTATE field is consumed, the server checks the current Page's ViewStateUserKey property and validates it against the value that was used to generate the __VIEWSTATE field. If the values do not match, the request is rejected as potentially malicious.
An example of a ViewStateUserKey-related failure would be a client who has two tabs open in the browser. The client is logged in as User A, and in the first tab, a Page is rendered with a __VIEWSTATE whose ViewStateUserKey property contains "User A." In the second tab, the client logs out and then logs back in as User B. The client goes back to the first tab and submits the form. The ViewStateUserKey property might contain "User B" (because that is what the client's authentication cookie says). However, the __VIEWSTATE field that the client submitted contains "User A." This mismatch causes the failure.
- Resolution 4a: Verify that ViewStateUserKey is set correctly
If your application uses the ViewStateUserKey property, verify that the property's value is the same both when view state is generated and when it is consumed. If you are using the current logged-in user's username, make sure that the user is still logged in and that the user's identity has not changed at the time of postback. If you are using the current user's session identifier, make sure that the session hasn't timed out.
If you are running in a farm environment, make sure that the <machineKey> elements match. See Appendix A for instructions about how to generate these elements.
Appendix A: How to generate a <machineKey> element
Security warning
There are many web sites that will generate a <machineKey> element for you with the click of a button. Never use a <machineKey> element that you obtained from one of these sites. It is impossible to know whether these keys were created securely or if they are being recorded to a secret database. You should only ever use <machineKey> configuration elements that you created yourself.
There are many web sites that will generate a <machineKey> element for you with the click of a button. Never use a <machineKey> element that you obtained from one of these sites. It is impossible to know whether these keys were created securely or if they are being recorded to a secret database. You should only ever use <machineKey> configuration elements that you created yourself.
To generate a <machineKey> element yourself, you can use the following Windows PowerShell script:
# 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)
}
}
For ASP.NET 4.0 applications, you can just call Generate-MachineKey without parameters to generate a <machineKey> element as follows:
PS> Generate-MachineKey
<machineKey decryption="AES" decryptionKey="..." validation="HMACSHA256" validationKey="..." />
ASP.NET 2.0 and 3.5 applications do not support HMACSHA256. Instead, you can specify SHA1 to generate a compatible <machineKey> element as follows:
PS> Generate-MachineKey -validation sha1
<machineKey decryption="AES" decryptionKey="..." validation="SHA1" validationKey="..." />
As soon as you have a <machineKey> element, you can put it in the Web.config file. The <machineKey> element is only valid in the Web.config file at the root of your application and is not valid at the subfolder level.
<configuration>
<system.web>
<machineKey ... />
</system.web>
</configuration>
For a full list of supported algorithms, run help Generate-MachineKey from the Windows PowerShell prompt.
Appendix B: Provisioning the registry to persist auto-generated keys
By default, because ASP.NETs auto-generated keys are persisted in the HKCU registry, these keys may be lost if the user profile hasn’t been loaded into the IIS worker process and then the application pool recycles. This scenario could affect shared hosting providers that are running application pools as standard Windows user accounts.To work around this situation, ASP.NET enables persisting the auto-generated keys in the HKLM registry instead of the HKCU registry. This is typically performed by using the aspnet_regiis utility (see instructions in the "Resolution 2a: Use the aspnet_regiis utility" section). However, for administrators who do not want to run this utility, the following Windows PowerShell script may be used instead:
# 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)
}
}
}
The following example shows how to provision the appropriate HKLM registry entries for an application pool that runs as the user, example@contoso.com (this is the UPN of the Windows user account). This application pool is a 32-bit application pool that is running the CLR v2.0 (ASP.NET 2.0 or 3.5).
PS> Provision-AutoGenKeys -FrameworkVersion 2.0 -Architecture 32 -UPN "example@contoso.com"
If the application pool instead is a 64-bit application pool that is running CLR v4.0 (ASP.NET 4.0 or 4.5), the command is as follows:
PS> Provision-AutoGenKeys -FrameworkVersion 4.0 -Architecture 64 -UPN "example@contoso.com"
Even though the auto-generated keys are stored in HKLM, the registry subkey that holds each user account’s secret cryptographic material is added to an access control list (ACL) so that the cryptographic material cannot be read by other user accounts.
Appendix C: Encrypting the <machineKey> element in configuration files
Server administrators may not want highly sensitive information such as the <machineKey> key material to bein plaintext form in configuration files. If this is the case, administrators may decide to take advantage of a .NET Framework feature known as "protected configuration." This feature lets you encrypt certain sections of the .config files. If the contents of these configuration files are ever disclosed, these sections' contents will still remain secret.You can find a brief overview of protected configuration on the MSDN website. It also contains a tutorial about how to protect the <connectionStrings> and <machineKey> elements of the Web.config file.