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? 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. 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. 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. 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: 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.) Figure 2:
Rendering code for the HiddenField control. In ASP.NET 1.1, the LosFormatter class has two
constructors: the standard parameterless one plus the one shown here: 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. Figure 3: Create a
MAC key code to avoid tampering. 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. The
sample code accompanying this article is available for download. Motivation for Hidden Fields in ASP.NET
Introducing the HiddenField Control
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
}
}
ReadOnly Hidden Fields
string GetValueToStore(string valueToStore)
{
StringWriter writer = new StringWriter();
LosFormatter formatter = new LosFormatter();
formatter.Serialize(writer, valueToStore);
return writer.ToString();
}
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();
}
Add Protection Against Tampering
public LosFormatter(bool enableMac, string macKeyModifier)
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;
}
Putting It All Together
Figure 4: The sample page in action.
Protect Your Hidden Fields
Fend Off Tampering with Custom Hidden Fields
0 comments
Hide comments