Working with ViewState

Introduction

The purpose of this technical post is to introduce the main variables that affect how ViewState is stored in a page.  I wrote this article as a summary of my findings after spending hours trying to diagnose various recurring exceptions in my websites' event logs which turned out to be related to ViewState security.

First we'll look at ASP.NET's default settings and what they mean for your ASP.NET page.

Then we'll have a quick look at where encryption keys come from, when they are required.

Finally, we'll highlight some of the things that can go wrong, and how they could be diagnosed given the information in this post.

Assumed Knowledge

This post assumes you already understand the role of ViewState in the Page Lifecycle.  For a refresher, take a look at the MSDN article:

http://msdn.microsoft.com/en-us/library/ms972976.aspx#viewstate_topic2

ASP.NET Default Settings

web.config

Let's start with the relevant defaults used by ASP.NET if you don't change anything yourself.  This are mostly determined by your web.config file (with one exception, covered in the text).

I've copied the following extract from web.config.comments (which you should be able to find tucked away in your Windows directory, if you have .NET installed) that shows the relevant defaults & possible values:
<pages
	enableViewState = "true" [true|false]
	enableViewStateMac = "true" [true|false]
	viewStateEncryptionMode = "Auto" [Auto | Always | Never]
>

enableViewState

ViewState is always used, by default.  So where is it stored?  A field will be added to your page, typically in a <div> element just inside the <form> element, looking like this:

<input id="__VIEWSTATE" type="hidden" value="/wEPDwUJODM...==" 
	name="__VIEWSTATE"/>
I've truncated the value which may be big or small depending on how much data is stored.

viewStateEncryptionMode

The ViewState value can be encrypted to stop users (maliciously or otherwise) changing it, before a PostBack.

The default setting of viewStateEncryptionMode is "Auto".  This means that ASP.NET will only encrypt the ViewState if a control on the page requests it.  For example, the GridView, DetailsView and FormView will request encryption when the DataKeyNames property is set.

The quick way to discover if encryption is being used or not is to look at the bottom of your form, typically in a <div> element.  If encryption is in use, ASP.NET adds a field with no value, to indicate this:

<input id="__VIEWSTATEENCRYPTED" type="hidden" name="__VIEWSTATEENCRYPTED" />
The encrypted ViewState is still stored in the same field as the unencrypted ViewState.

You can, of course, force encryption on or off using the "Always" or "Never" values in web.config.

enableViewStateMac

The Message Authentication Code (MAC) is the hash of the ViewState value (encrypted or unencrypted).  The MAC code is always the same length (as is the nature of a hashing algorithm) and is added to the end of the ViewState's value.

Its purpose is to allow ASP.NET to check that the hash it sent out with the page matches the hash of the ViewState submitted during a PostBack.  If it is different, the ViewState may have been tampered with and an exception will be thrown.

ASP.NET also allows you to increase the security of the MAC, by adding in a value that is unique to the current user, using a Page property:
Page.ViewStateUserKey
If you specify a value (for example, the Session ID, or a user's log-in name) then this is incorporated into the MAC, but only if:
enableViewStateMac = "true" 
in web.config (which is the default anyway).

For those of you using Kentico CMS V4.0, the ViewStateUserKey is populated by default.  You can turn this off in your web.config file using:
<app Settings
<add key="CMSUseViewStateUserKey" value="false"/>

Encryption Keys

web.config

If your ViewState is to be encrypted, then you require an encryption algorithm & key.

Similarly, if you want a MAC to be created as a hash of your ViewState you need a hashing algorithm & key.

These are again specified within web.config, with the options as follows:
<machineKey
validationKey="AutoGenerate,IsolateApps" [String]
decryptionKey="AutoGenerate,IsolateApps" [String]
validation="SHA1" [SHA1 | MD5 | 3DES | AES]
decryption="Auto" [Auto | DES | 3DES | AES]
/> (decryption actually uses AES when "Auto" is selected)
Here, the validationKey is used for hashing & the decryptionKey for encryption/decryption.

The default is for ASP.NET to make the decisions on algorithms for you, and to create unique, occasionally changing keys for them.  These will be fine for many situations, but you will need to specify your own values for the situations outlined in the next section.

Web Farms & Shared Hosts

When ASP.NET generates the keys for you, the keys will be derived from the ID of the application.  If there is any chance of these keys changing between the time a user requests a page and then sending a PostBack of the page, you must manually specify your own, fixed, keys.

In a web farm, each separate server will generate its own keys, so you must create your own & manually add them to web.config on each machine.  Otherwise if a user happened to PostBack to a different server, an exception would be generated.

In shared hosting, you may be lucky & be able to use the defaults.  However, the application pool may be restarted at any time (whether planned or due to another user's site crashing) & new keys created while a user is halfway through using a page, again causing an exception.

So you need to add your own keys if either of these situations apply to you.  There are many online utilities for creating these for you.  One I have found for you (but never used myself) is:

www.aspnetresources.com/tools/keycreator.aspx

The output which you need to add within the system.web section of web.config will look something like this:
<machineKey
validationKey="70723A457658C5......2E77"
decryptionKey="8B2070C36F......76E31"
validation="SHA1" />

Diagnosing Problems

The exceptions I came across seemed to divide into a number of categories:

Encryption Keys Changed Before PostBack

A typical exception message for this is, "Validation of viewstate MAC failed".  It is saying that when the server recalculated the Message Authentication Code for the ViewState it received in a PostBack, it didn't match the value it sent out.

So the most likely causes are either someone has been tampering with the ViewState, or the encryption keys have changed in the meantime.

I do recommend having a close look at your server logs, if you can find the particular entries for the page that caused the problem (by cross-referencing the time & IP address from your exception details). It won't tell you if the keys changed, but you may be able to see whether the pattern of pages requested looks like a genuine user or someone probing your site.

But in general, the solution to this type of problem is to add your own encryption keys to the <machineKey> element in web.config, as outlined above, so removing the possibility of the keys changing unexpectedly.

Wrong ViewState Used Due To Caching

If a particular page uses full-page caching to improve performance, then you must be sure the ViewState is the same for all users who will use that cached version. 

A typical "gotcha" would be to carefully set the property (described above):
	Page.ViewStatUserKey

to a value unique to each user, to improve security.  But if full-page caching is in use, then all the users would receive identical copies of the page to the first person who requested the page.  The first person is fine, but everyone else will generate an exception when they PostBack their copy of the page, which has the MAC for the first person in the ViewState, rather than their own.

Partial Page Submitted During PostBack

This sort of exception could result in a message like, "Unable to validate data".

The problem can happen when the user has requested a PostBack before the original page has finished loading, so that when the application inspects what it has received, it cannot make sense of the ViewState.

For example, as outlined in the viewStateEncryptionMode section above, the flag to indicate encryption is in use is rendered right at the end of the form:
<input id="__VIEWSTATEENCRYPTED" type="hidden" name="__VIEWSTATEENCRYPTED" />
So if the page is taking a long time to download, but has started to render in the browser, the user may be able to click on a button (say) to create a PostBack before this field has been added to the page.

Then when the application inspects the PostBack (from only the partial page) it will assume it is not encrypted, since there is nothing to say that it is, and an exception will be raised.

Note that it seems this issue may have been addressed in Service Pack 1 for ASP.NET 3.5, but I've not yet tested this for myself.

Conclusion

Hopefully this article will help give you some context when trying to diagnose problems linked to ViewState.  Please feel free to correct or add to the article using the Comments section below. 
Comments
Ezeki
thanks for this topic, it was very helpful for me
25/08/2009 11:02:10
 
Jeff Prince
Steve,
Thanks a million for clarifying this ViewStateMAC issue. It has been a thorn in my side, and I now realise that I have to fix the decryptionkey as well as the validationkey to solve my problems.
Jeff Prince
30/04/2010 15:41:21
 
Leave comment



 Security code