Web Debug

Fix broken web applications, from servers to clients.

ViewStateUserKey and ViewStateMac validation failure

Previously I have posted on the validation of viewstate MAC failure. We already know that the validationKey and validation algorithm need to be the same across all the servers in a load-balanced environment. But user still see viewState validation failures in event logs.

ViewStateUserKey

There is another factor that could be playing in the middle. That is the ViewStateUserKey. Microsoft® ASP.NET version 1.1 added an additional Page class property—ViewStateUserKey. This property, if used, must be assigned a string value in the initialization stage of the page life cycle (in the Page_Init event handler). The point of the property is to assign some user-specific key to the view state, such as a username. The ViewStateUserKey, if provided, is used as a salt to the hash during the MAC.

What the ViewStateUserKey property protects against is the case where a nefarious user visits a page, gathers the view state, and then entices a user to visit the same page, passing in their view state.

ms972976.viewstate_fig10(en-us,MSDN.10).gif

 


ASP.NET Implementation

If we reflect the System.Web.dll and look into the implementation, we will see the method below is taking ViewStateUserKey as a hash modifier which combines with validationKey to compute the hash for the viewstate.

// System.Web.UI.ObjectStateFormatter
private byte[] GetMacKeyModifier()
{
if (this.macKeyBytes == null)
{
if (this.
page == null)
{
return null;
}
uint clientStateIdentifier = this.page.GetClientStateIdentifier();
string viewStateUserKey = this.
page.ViewStateUserKey;
if (viewStateUserKey != null)
{
int byteCount = Encoding.Unicode.GetByteCount(viewStateUserKey);
this.macKeyBytes = new byte[byteCount + 4];
Encoding.Unicode.GetBytes(viewStateUserKey, 0, viewStateUserKey.Length, this.
macKeyBytes, 4);
}
else
{
this.macKeyBytes = new byte[4];
}
this.
macKeyBytes[0] = (byte)clientStateIdentifier;
this.macKeyBytes[1] = (byte)(clientStateIdentifier >> 8);
this.
macKeyBytes[2] = (byte)(clientStateIdentifier >> 16);
this.macKeyBytes[3] = (byte)(clientStateIdentifier >> 24);
}
return this.
macKeyBytes;
}

// System.Web.Configuration.MachineKeySection
private static byte[] HashDataUsingNonKeyedAlgorithm(HashAlgorithm hashAlgo, byte[] buf, byte[] modifier, int start, int length, byte[] validationKey)
{
int num = length + validationKey.Length + ((modifier != null) ? modifier.Length : 0);
byte[] array = new byte[num];
Buffer.BlockCopy(buf, start, array, 0, length);
if (modifier != null)
{
Buffer.BlockCopy(modifier, 0, array, length, modifier.Length);
}
Buffer.BlockCopy(validationKey, 0, array, length, validationKey.Length);
if (hashAlgo != null)
{
return hashAlgo.ComputeHash(array);
}
byte[] array2 = new byte[16];
int sHA1Hash = UnsafeNativeMethods.GetSHA1Hash(array, array.Length, array2, array2.Length);
Marshal.ThrowExceptionForHR(sHA1Hash);
return array2;
}

Resolution

Usually we would set the ViewStateUserKey are set to sessionID during the pageinit stage, that works well in a single server, however in a load-balanced server environment, we need to make sure the sessionID for one user is consistent across all the servers,

  • Make sure the servers are using the same state server
  • Make sure not to cache the pages with cookies
  • Make sure session expiration faster than the authentications

Also we can use choose to use user name instead of sessionID when authenticated as the user is always consistent across all the servers.

void PageInit (Object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
ViewStateUserKey = User.Identity.Name;
}

 

Reference

Understanding ASP.NET View State

http://msdn.microsoft.com/library/ms972976.aspx

Take Advantage of ASP.NET Built-in Features to Fend Off Web Attacks

http://msdn.microsoft.com/en-us/library/ms972969.aspx

ViewStateUserKey makes ViewState more tamper-resistant

http://www.hanselman.com/blog/ViewStateUserKeyMakesViewStateMoreTamperresistant.aspx

Cross-site request forgery

http://en.wikipedia.org/wiki/Cross-siterequestforgery

Fork me on GitHub