The purpose of this 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.
This post assumes you already understand the role of ViewState in the Page Lifecycle. For a refresher, take a look at the MSDN article:
ASP.NET Default Settings
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] >
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.
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.
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.ViewStateUserKeyIf 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 anyone using Kentico CMS (V4 onwards), the ViewStateUserKey is populated by default with the value:
HttpContext.Current.Request.UserHostAddress + "_" + HttpContext.Current.Session.SessionID
You can turn this off in your web.config file using:
<app Settings <add key="CMSUseViewStateUserKey" value="false"/>
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 mustcreate 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:
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" />
The exceptions I came across seemed to divide into a number of categories:
This site regularly gets the exception message "Invalid viewstate" in the logs, and is an example of validation working correctly. It seems to happen when an automated web-spider is probing the site and sees that the page has a form with a post-back location specified:
<form method="post" action="/default.aspx" id="form1">
The spider then constructs a POST request to the URL specified (presumably to see if this reveals anything that can be used to compromise the site). But because this POST request does not include valid ViewState, the error is thrown.
If you are seeing this exception regularly, then it is worth finding some of the offending requests in the website logs to see if this diagnosis is relevant to you.
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):
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 viewStateEncryptionModesection 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.