6 Comments

Introduction

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.

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:

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 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"/>

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 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" />

Diagnosing Problems

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

Invalid ViewState

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):

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 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.

Comments

Comment by Ezeki

thanks for this topic, it was very helpful for me

Ezeki
Comment by 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

Comment by Rhino Car Hire

Hi Steve,

I found this blog from the kentico forum where you posted a question on the "Unable to validate data"

I have been getting this issue for a while now but it only seems to happen on this page http://www.rhinocarhire.com/Customer-Services.aspx which is odd.

Also, I do not have any of the below in my webconfig, do you think I should add them. I do have the machinekey decryptionkey and validationkey set.

enableViewState = "true" [true|false]
enableViewStateMac = "true" [true|false]
viewStateEncryptionMode = "Auto" [Auto | Always | Never]

I also seem to get this error but only from googebot but wonder if its related:

"Padding is invalid and cannot be removed"

Thanks

Scott

Comment by Steve Moss

Hi Scott

You only need to add the web.config settings you highlighted if you want to change the defaults (which since the defaults are true, means you would be turning things off).

My Kentico sites do still generate the same exceptions you are getting, from time to time, despite having the configuration as I've described.

The next step in diagnosis is to find the exact requests in your web site logs that caused each exception.

You may find that the "Unable to validate data" error is only caused by search engines, perhaps doing a postback from a cached copy of a page. Since the ViewStateMac includes some session identifier, this would most likely be wrong for the current session and hence you get an exception.

It's a bit tedious, but you need to go through the logs this way to work out whether real users are causing the problem, or if it is just a side-effect from search-engine activity, or even hackers probing the site.

Hope this helps.

Steve

Steve Moss
Comment by Victor Juri

I hate viewstate for this reason and when I can avoid it I do. I try to use MVC for this reason. Nice and restful.

I have to say this article was very helpful and helped me resolve my problem. I specified a machine key and the default kentico key where conflicting causing the viewstate validation issue. Once I added <add key="CMSUseViewStateUserKey" value="false"/> view state was only encoded with my machine key. Problem solved. Hope it helps someone else.

Comment by Steve Moss

Hi Victor

Yes I agree ViewState is a pain. For new applications we always look at Microsoft's MVC framework first, too.

In most instances I'd recommend that you keep CMSUseViewStateUserKey = true. Don't forget that it is normal to expect some exceptions to be thrown related to ViewState where there were genuine "attacks" that the site has defended you against.

Steve Moss