Protect Your Hidden Fields

Fend Off Tampering with Custom Hidden Fields

Dino Esposito

October 30, 2009

9 Min Read
ITPro Today logo in a gray background | ITPro Today

Although rarely asked publicly, a question I often get atconferences and classes concerns the use of hidden fields inASP.NET. Hiddenfields have a bad reputation among Web developers probably because theyappear to be a quick fix and a sort of dirty trick. For some reason, developerstend to think that they use hidden fields because they re unable to find a bettersolution.

In the past, most of us simply employed hidden fields inASP without much concern and with no ounce of precaution. Nevertheless,hidden form fields aren t hidden very well. For a potential attacker, snoopinginto a hidden field is as easy as looking into the window opened through theView Source menu item. Hidden fields are hidden only from the browser s view ofthe page; in no way are they out of reach of malicious users. A malicious usercan then simply save the page, modify any hidden fields he finds interesting,and reload the page in the browser: new values will be posted to the server.Does this mean that you simply have to quit using hidden fields?

 

Motivation for Hidden Fields in ASP.NET

To mitigate risks with hidden fields, you can simply leavethe information stored in a session state slot. However, this solution is notfree of drawbacks either, because it taxes the always valuable server memory.With the viewstate, ASP.NET provides a state storage mechanism that is bothclient-side and nearly as secure as the server-side session state. ASP.NETviewstate cannot be tampered with (unless you guiltily weaken default securitysettings), but that doesn t guarantee data confidentiality. In addition, itdoesn t expose itself to server-side crashes and failures; viewstate, though,is a potential burden for all requests because of the extra data appended thataffects both the page download and upload.

Do you really need to use custom hidden fields in ASP.NET?And if yes, when? Viewstate is good at persisting state; it doesn t serve as away to exchange data between rich server controls and their client tagcounterpart. In addition, viewstate is an application-level feature that can bedisabled without notice by page developers. In MaintainState Control, I discussed control state, which is an upcoming feature ofASP.NET 2.0 specifically designed to let custom controls persist their mostcritical state (a small portion of the entire state) in a way that nopage-level feature can ever disable. In doing so, I created an ASP.NET 1.xversion of the ASP.NET 2.0 control state.

A few readers commented about my article Maintain State Control, arguing that my control state implementation isn t aspowerful and secure as the original viewstate. Note that in ASP.NET 2.0,viewstate and control state are packed into the same hidden field. In theimplementation from MaintainState Control, I used the LosFormatter class to scramble the contents ofthe field, but omitted any further measures against tampering. I m up forfinding an effective remedy. I ll go one step further and discuss a newHiddenField control. A similar control is completely new to ASP.NET 1.1, butpart of the standard toolbox in ASP.NET 2.0.

 

Introducing the HiddenField Control

In ASP.NET 2.0, the HiddenField control wraps the HTML tag and abstracts the programming interface ofthe ASP.NET 1.1 HtmlInputHidden control. In this article, I ll build a newhidden field control that can be ported to ASP.NET 2.0 with virtually nochanges. The new control extends the native ASP.NET 2.0 HiddenField control intwo areas: the possibility of transmitting protected data and optional supportfor a machine authentication code (MAC) key to make the transmitted datavirtually untouchable. By the way, the MAC key is what makes the ASP.NETviewstate tamper resistant.

Figure 1 shows the foundation of the code for an ASP.NETcross-version HiddenField control. The control inherits from Control andimplements the IPostBackDataHandler interface. It features a Value stringproperty and raises the ValueChanged event whenever the host page posts backwith a different value in the field.

namespace AspNetPro.Controls{ public class HiddenField : Control, IPostBackDataHandler { public HiddenField() {} public virtual string Value {    get {       string tmp = (string) ViewState["Value"];       if (tmp == null)           return String.Empty;       return tmp;    }    set {ViewState["Value"] = value;} } public event EventHandler ValueChanged; protected virtual void OnValueChanged(EventArgs e) {    if (ValueChanged != null)        ValueChanged(this, e); } bool LoadPostData(string postDataKey,                   NameValueCollection postCollection) {    // code omitted for brevity } void RaisePostDataChangedEvent()   {    // code omitted for brevity }}

Figure 1:Structure of the HiddenField control.

There are mainly two scenarios in ASP.NET where theHiddenField control would easily fit in: data exchange with the client andpersistent client-side storage of a fragment of the page, or control state.

When you re writing a rich, custom control you might needto post some free-form information from the client to the server. For example,imagine you re designing a grid control that allows for column reorderingthrough drag-and-drop. The new order of the columns is determined on the clientand must be passed to the server. The grid, on the other hand, has no userinterface that can accept data no input fields or dropdown list. In thiscontext, a hidden field is a fair way to post custom data to the server. Ifused in this way, the hidden field must be read/write and as such inevitablyexposes itself to the risk of tampering. Encoding and encryption are possiblein theory, but are not really practical because the logic must be on the clientand the server; this is nothing an attacker couldn t guess.

Whatwould be needed is a hidden field that behaves like the viewstate hidden field:unintelligible to read and sensitive enough to detect tampering.

 

ReadOnly Hidden Fields

Just before the ASP.NET page renders any contents out, itsaves the viewstate to the __VIEWSTATE hidden field. The data stored in theViewState collection is serialized to an array of bytes, added a hash key, andthen encoded using the Base64 algorithm. The hash key is what allows you todetect tampering once the page posts to the server. The hash key is a valuecalculated on the contents ofthe viewstate and influenced by a variety of parameters, including thevalue programmatically stored in the ViewStateUserKey property of the Pageclass, the page s type name, and its template source directory name. The hashkey is not reproducible on the client, thus invalidating any attacker s attemptto modify the state of the page.

Suppose a malicious user modifies the viewstate on theclient and then posts the page back. Evena little change in the source data unpredictably alters the hash key. In thisway, the attacker can t recalculate the correct hash for the modified stateunless he or she knows about the server-side values used to generate the key.Suppose, though, the attacker insists and sends a manually created viewstate. Whathappens on the server? The ASP.NET runtime extracts the hash key from theincoming viewstate and recalculates the hash key on the posted values and theserver constants. If the two keys match, the request is allowed to proceed;otherwise, an exception is thrown. The goodness of the hash algorithm (andsubsequently the scarce likelihood of guessing the new hash key) is theguarantee of the viewstate security. The question now is, how can I get this towork for my own hidden fields?

The LosFormatter class helps a lot (again, see my previousarticle MaintainState Control). The following code snippet shows how to pass a plain stringto a function and get a viewstate-like encoded string back:

string GetValueToStore(string valueToStore){ StringWriter writer = new StringWriter(); LosFormatter formatter = new LosFormatter(); formatter.Serialize(writer, valueToStore); return writer.ToString();}

All the magic takes place within the LosFormatter class.Well, actually not all of it, as some readers pointed out. Figure 2demonstrates the rendering code of the HiddenField control. Where s the point?LosFormatter, as called in the code, encodes the value, but adds no hash key.As a result, no error is detected on the server if a user happens to modify thehidden field. The modified text shows up as long as it can be correctlydecoded. (If you change characters randomly to test what happens, you might getoccasional deserialization exceptions.)

protected override void Render(HtmlTextWriter writer){ if (Page != null)  Page.VerifyRenderingInServerForm(this); writer.AddAttribute(HtmlTextWriterAttribute.Type,                     "hidden"); string name = this.UniqueID; if (name != null)  writer.AddAttribute(HtmlTextWriterAttribute.Name, name);     if (ID != null)  writer.AddAttribute(HtmlTextWriterAttribute.Id,                      ClientID); string fieldValue = Value; if (fieldValue.Length > 0) {  string encText = EncodeText(fieldValue);  writer.AddAttribute(HtmlTextWriterAttribute.Value,                      encText); } writer.RenderBeginTag(HtmlTextWriterTag.Input); writer.RenderEndTag();}private string EncodeText(string fieldValue){ if (!EnableProtection)    return fieldValue;     StringWriter writer = new StringWriter(); LosFormatter formatter = GetFormatter(); formatter.Serialize(writer, fieldValue); return writer.ToString();}

Figure 2:Rendering code for the HiddenField control.

 

Add Protection Against Tampering

In ASP.NET 1.1, the LosFormatter class has twoconstructors: the standard parameterless one plus the one shown here:

public LosFormatter(bool enableMac, string macKeyModifier)

If you pass true to the first parameter and a properstring to the second, you ll have the hidden field enjoy the same type oftampering protection as the view state. In ASP.NET 2.0, the class adds a thirdconstructor that accepts an array of bytes instead of a MAC key string. Theonly remaining problem is how to get a MAC key modifier. You have to build it.The Page class has a method that does that, but it s a private method. Figure 3shows a possible approach.

In the example, the MAC key is an array of two logicalelements: a hash code and the viewstate user s key. The hash code is calculatedfrom the page s source directory (the TemplateSourceDirectory property) and thename of the class. This number occupies position #0 in the resulting byte array.The second logical element copied one byte after the next in the same array isthe content of the Page s ViewStateUserKey property. This property should beset to a value unique per user, such as the session ID or the user s name ifauthenticated. The structure of the code in Figure 3 mimics the real code usedby ASP.NET to protect the viewstate, but differs from it in several aspects.

private string macKey = "";private string GetMacKeyModifier(){ byte[] macKeyModifier = null; if (macKey.Length == 0) {   IHashCodeProvider hash =    new CaseInsensitiveHashCodeProvider();   int code =    hash.GetHashCode(Page.TemplateSourceDirectory);   code += hash.GetHashCode(base.GetType().Name);   if (Page.ViewStateUserKey != null)   {    int len =     Encoding.Unicode.GetByteCount(Page.ViewStateUserKey);     macKeyModifier = new byte[len + 1]; Encoding.Unicode.GetBytes(Page.ViewStateUserKey, 0, Page.ViewStateUserKey.Length, _macKeyModifier, 1);   }   else     macKeyModifier = new byte[1];   macKeyModifier[0] = (byte) code;   macKey = macKeyModifier.ToString(); } return macKey;}

Figure 3: Create aMAC key code to avoid tampering.

 

Putting It All Together

So now you have a new ASP.NET 1.1 HiddenField control thatcan be ported to ASP.NET 2.0 with zero costs. The new control differs fromHtmlInputHidden and the ASP.NET 2.0 s HiddenField control for a couple ofBoolean properties: EnableProtection and DetectTampering. The former enablesthe use of LosFormatter to serialize and deserialize the contents of the hiddenfield. The latter uses the version of LosFormatter that supports the MAC code.By using the HiddenField control you can avoid using the Page.RegisterHiddenFieldmethod programmatically; simply place the control declaratively in the ASPXsource and go:

The sample page in Figure 4 creates a hidden field with adefault value. Through the View Source menu item you see that the item isdownloaded in an encoded form and posted back correctly. Other buttons let youview and edit the contents of the hidden field; if you post back modifiedcontent, an exception will be raised.


Figure 4: The sample page in action.

Thesample code accompanying this article is available for download.

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like