Protect Your Hidden Fields

Fend Off Tampering with Custom Hidden Fields

Although rarely asked publicly, a question I often get at conferences and classes concerns the use of hidden fields inASP.NET . Hidden fields have a bad reputation among Web developers probably because they appear to be a quick fix and a sort of dirty trick. For some reason, developers tend to think that they use hidden fields because they re unable to find a better solution.

In the past, most of us simply employed hidden fields in ASP without much concern and with no ounce of precaution. Nevertheless, hidden form fields aren t hidden very well. For a potential attacker, snooping into a hidden field is as easy as looking into the window opened through the View Source menu item. Hidden fields are hidden only from the browser s view of the page; in no way are they out of reach of malicious users. A malicious user can 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 leave the information stored in a session state slot. However, this solution is not free of drawbacks either, because it taxes the always valuable server memory. With the viewstate, ASP.NET provides a state storage mechanism that is both client-side and nearly as secure as the server-side session state. ASP.NET viewstate cannot be tampered with (unless you guiltily weaken default security settings), but that doesn t guarantee data confidentiality. In addition, it doesn t expose itself to server-side crashes and failures; viewstate, though, is a potential burden for all requests because of the extra data appended that affects 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 a way to exchange data between rich server controls and their client tag counterpart. In addition, viewstate is an application-level feature that can be disabled without notice by page developers. In Maintain State Control, I discussed control state, which is an upcoming feature of ASP.NET 2.0 specifically designed to let custom controls persist their most critical state (a small portion of the entire state) in a way that no page-level feature can ever disable. In doing so, I created an ASP.NET 1.x version 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 as powerful 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 the implementation from Maintain State Control, I used the LosFormatter class to scramble the contents of the field, but omitted any further measures against tampering. I m up for finding an effective remedy. I ll go one step further and discuss a new HiddenField control. A similar control is completely new to ASP.NET 1.1, but part 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 of the ASP.NET 1.1 HtmlInputHidden control. In this article, I ll build a new hidden field control that can be ported to ASP.NET 2.0 with virtually no changes. The new control extends the native ASP.NET 2.0 HiddenField control in two areas: the possibility of transmitting protected data and optional support for a machine authentication code (MAC) key to make the transmitted data virtually untouchable. By the way, the MAC key is what makes the ASP.NET viewstate tamper resistant.

Figure 1 shows the foundation of the code for an ASP.NET cross-version HiddenField control. The control inherits from Control and implements the IPostBackDataHandler interface. It features a Value string property and raises the ValueChanged event whenever the host page posts back with 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 the HiddenField control would easily fit in: data exchange with the client and persistent client-side storage of a fragment of the page, or control state.

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

What would 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, it saves the viewstate to the __VIEWSTATE hidden field. The data stored in the ViewState collection is serialized to an array of bytes, added a hash key, and then encoded using the Base64 algorithm. The hash key is what allows you to detect tampering once the page posts to the server. The hash key is a value calculated on the contents of the viewstate and influenced by a variety of parameters, including the value programmatically stored in the ViewStateUserKey property of the Page class, the page s type name, and its template source directory name. The hash key is not reproducible on the client, thus invalidating any attacker s attempt to modify the state of the page.

Suppose a malicious user modifies the viewstate on the client and then posts the page back. Even a little change in the source data unpredictably alters the hash key. In this way, the attacker can t recalculate the correct hash for the modified state unless 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. What happens on the server? The ASP.NET runtime extracts the hash key from the incoming viewstate and recalculates the hash key on the posted values and the server constants. If the two keys match, the request is allowed to proceed; otherwise, an exception is thrown. The goodness of the hash algorithm (and subsequently the scarce likelihood of guessing the new hash key) is the guarantee of the viewstate security. The question now is, how can I get this to work for my own hidden fields?

The LosFormatter class helps a lot (again, see my previous article Maintain State Control). The following code snippet shows how to pass a plain string to 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 2 demonstrates 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 the hidden field. The modified text shows up as long as it can be correctly decoded. (If you change characters randomly to test what happens, you might get occasional 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 two constructors: 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 proper string to the second, you ll have the hidden field enjoy the same type of tampering protection as the view state. In ASP.NET 2.0, the class adds a third constructor that accepts an array of bytes instead of a MAC key string. The only 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 3 shows a possible approach.

In the example, the MAC key is an array of two logical elements: a hash code and the viewstate user s key. The hash code is calculated from the page s source directory (the TemplateSourceDirectory property) and the name 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 is the content of the Page s ViewStateUserKey property. This property should be set to a value unique per user, such as the session ID or the user s name if authenticated. The structure of the code in Figure 3 mimics the real code used by 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 a MAC key code to avoid tampering.

 

Putting It All Together

So now you have a new ASP.NET 1.1 HiddenField control that can be ported to ASP.NET 2.0 with zero costs. The new control differs from HtmlInputHidden and the ASP.NET 2.0 s HiddenField control for a couple of Boolean properties: EnableProtection and DetectTampering. The former enables the use of LosFormatter to serialize and deserialize the contents of the hidden field. The latter uses the version of LosFormatter that supports the MAC code. By using the HiddenField control you can avoid using the Page.RegisterHiddenField method programmatically; simply place the control declaratively in the ASPX source and go:

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


Figure 4: The sample page in action.

The sample code accompanying this article is available for download.

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish