HP Security Products Blog
From applications to infrastructure, enterprises and governments alike face a constant barrage of digital attacks designed to steal data, cripple networks, damage brands, and perform a host of other malicious intents. HP Enterprise Security Products offers products and services that help organizations meet the security demands of a rapidly changing and more dangerous world. HP ESP enables businesses and institutions to take a proactive approach to security that integrates information correlation, deep application analysis and network-level defense mechanisms—unifying the components of a complete security program and reducing risk across your enterprise. In this blog, we will announce the latest offerings from HP ESP, discuss current trends in vulnerability research and technology, reveal new HP ESP security initiatives and promote our upcoming appearances and speaking engagements.

ASP.NET Cross-Site Scripting Followup: Mono

While doing the research that led to my recent post on ASP.NET view state as a vector for XSS, I discovered that while Microsoft's implementation is secure by default, Mono was not.

The default for a Page's EnableViewStateMac property is true in Microsoft's .NET Framework (despite what some documentation says). In Mono, this defaulted to false. In addition, there were certain problems with configuration which made it more difficult to mitigate this vulnerability:

In web.config, setting:

<system.web> <pages enableViewStateMac="true" />

did not enable view state signing. Neither did setting

<%@ Page EnableViewStateMac="true" %>

inside a page's .aspx file. While these two may be considered ordinary bugs and not directly a security problem, they made it much more difficult to work around the problem for existing installations.

Although Mono's view state format is somewhat different from Microsoft's, it was possible to port the exact same attack I used on Microsoft's ASP.NET to Mono by changing the data structure slightly and using Mono's serializer.

The following link will display a Javascript alert when running the XSP sample project on Mono 2.6.3: http://localhost:8080/2.0/menu/menu1.aspx?__VIEWSTATE=DAwNEAIAAA4BBQEOAQ0QAg8BAQlpbm5lcmh0bWwBJzxzY3JpcHQ%2BYWxlcnQoJ0FTUC5ORVQgWFNTIScpOzwvc2NyaXB0PhAAAAAOAAAA

Affected Versions

Mono 2.6.3 and older

Fixed Versions

Mono 2.6.4
Fixes for older branches should be committed soon

Other info


Configuration is Half the Battle: ASP.NET and Cross-Site Scripting

Although it's not a new problem, a recent advisory and BlackHat presentation have brought attention to an ASP.NET mis-configuration that can leave you wide open to Cross-Site Scripting (XSS) attacks, even if you are diligently sanitizing your other user-supplied data. If the view state is not cryptographically signed, it is possible for an attacker to overwrite properties of any of your server-side controls and modify HTML returned to the user, opening a vector for XSS.

On the Attack

An early example from the 2004 article “Understanding ASP.NET View State” does not really explain the full scope of the problem:

“Nefarious users could parse the view state, modify the prices so they all read $0.01, and then deserialize the view state back to a base-64 encoded string. They could then send out e-mail messages or post links that, when clicked, submitted a form that sent the user to your product listing page, passing along the altered view state in the HTTP POST headers. Your page would read the view state and display the DataGrid data based on this view state. The end result? You'd have a lot of customers thinking they were going to be able to buy your products for only a penny!”

The potential for damage is much worse than that, and the attack is even easier to carry out.

  1. It’s not necessary to fully parse the view state. Without the signature, the only protection the view state has left is the page hash, which always occurs at the same location and can be extracted by just Base64-decoding the right bytes.

  2. As an extension of #1, it’s not necessary to modify properties that are already being put in the view state. The view state parser does not expect any particular properties to be set (or not set), so you can modify nearly anything you want.

  3. The actual attack can, of course, consist of a malicious script and not just modified text.

  4. It’s not necessary to POST the view state data. In fact, the postback event validation makes it even more difficult. Simply encoding the view state data in a GET parameter named __VIEWSTATE will allow you to provide a malicious link to be clicked, not require a user to post a form.

The attacks I created for WebInspect were based on two more discoveries:

  1. Nearly every ASP.NET control is vulnerable (especially ASP.NET 2.0)

  2. A vulnerable control will almost always appear at index 1

The first point comes from the fact that most ASP.NET controls inherit from HtmlContainerControl, which has an InnerHtml property. The results of modifying InnerHtml should be obvious. While ASP.NET 1.1 would throw an exception if you tried to set that property on another type of control, it seems that most ASP.NET 2.0 controls will just set it as an attribute after performing a weak HTML-encoding. This allows for an easy attribute-based XSS attack, even if you can’t set the inner HTML of the control, leaving nearly all classes of controls vulnerable. Actually finding a control to attack brings me to point #2: how the control indexes work. On the ‘base’ page state, the list of control indexes corresponds to the order that they appear on the page. All text that is not part of a server-side control gets placed in a LiteralControl. This does not take open/closing tags into consideration, which is why most controls will first appear at index 1; index 0 will contain a LiteralControl for all text in the page (doctype, open tag, etc) leading up to it. The full list of controls, in general, alternates LiteralControls and subclasses of HtmlControl.

Detecting a Vulnerable ASP.NET Site

One of the biggest problems with this attack is how easy it is to detect a vulnerable site. In most cases, the actual exploit is also rather easy. WebInspect has been detecting an unsigned ASP.NET 1.1 view state since 2005, by just checking the last 2 bytes of the view state. A signed ASP.NET view state will end in 20 bytes of garbage, but an unsigned view state will end in “;>” or “>>”, due to the serialization format. We might get a false positive every 32k pages or so, but overall it’s a pretty effective test. In ASP.NET 2.0, the format changed to a binary serialization which requires reading the entire view state to determine if there is any extra data at the end (no more “<” and “>” tokens). It’s much slower, but still only a few lines of code when you use the ObjectStateFormatter available to you.

When doing my research for this vulnerability, I did a quick survey of 336 random sites running ASP.NET. Of those sites, I found 30 (9%) with unsigned view states. That may not sound like a lot compared to the number of total sites with XSS vulnerabilities (some estimates say at least half, others two-thirds). However, finding most XSS vulnerabilities usually requires checking hundreds of inputs with different kinds of validation. Finding a site with an unsigned view state is fast, simple and passive.

Of course, having an unsigned view state is not a guarantee of being exploitable. Some of those sites had a very minimal view state, which likely means that view state was disabled for the page (ASP.NET will still insert a “stub” view state). If your view state is disabled, disabling signing could be a legitimate performance improvement. If you are not disabling view state, but disabling signing, you’re almost certainly vulnerable.

Protect Yourself!

Protecting yourself is quite simple: don’t disable view state signing! It can be turned off in your web.config, a page’s .aspx file, or code-behind class. A full text search for “EnableViewStateMac“ should be all you need to check your own code. If you’re not changing it anywhere, you’re secure by default. WebInspect users can Smart Update to the latest SecureBase to get updated checks. We can detect an unsigned view state for ASP.NET 1.1 and 2.0 (and later; 3.5 still uses the same view state format), and attempt to attack both of them as well.

While you’re at it, there are a few other settings you may wish to review. If you are putting any sensitive information in the view state, it can be easily decoded and read by a 3rd party. You can set ViewStateEncryptionMode="Always" in your web.config or in individual pages. Since the signing key is always the same, it could be possible to construct a malicious, but valid, view state and then give the link to someone else, creating a Cross-Site Request Forgery attack (CSRF). This would be more difficult to execute than the XSS attack, but it’s just as easy to prevent. Set the ViewStateUserKey property in your page to a user-specific value, like the session ID. This adds a salt when signing, so that two users with the same view state data will have different signatures. If it were me, I would have all 3 options enabled, (salting, signing and encrypting), but everyone should evaluate the needs of their own applications.

Digging into ASP.NET RegEx Validators

RegEx Validators are handy for implementing Whitelist input validation (our DevInspect product has a library of a hundred or so) so it pays to see what they  actually do under the covers. The following code is from the class
System.Web.UI.WebControls.RegularExpressionValidator which implements the RegEx Validator in ASP.NET. I'm looking at version 2.0 of the .NET framework. The EvaluteIsValid() function is what actually determines if an input matches the allowed regex:

protected override bool EvaluateIsValid()
string controlValidationValue
= base.GetControlValidationValue(base.ControlToValidate);

if ((controlValidationValue == null)
|| (controlValidationValue.Trim().Length == 0))
return true;
Match match = Regex.Match(controlValidationValue,
return ((match.Success && (match.Index == 0))
&& (match.Length == controlValidationValue.Length));
return true;

Lets look at the first if statement closely. The conditional is a logical OR, so we can treat each part as individual if statements. The code says "if the control value is null, the input passes this validator." The control value can only be null if the control doesn't exist, and chances are you would have had a compile error. Either way, I don't really care about this statement.

The second part of the if statement is interesting. If the trimmed value of the input has no length, the input passes validation. This is a little weird. Trim() removes all leading and trailing whitespace from a string. Things like tab, carriage return, linefeed, etc. The full list is in the .NET String Class documentation. So input consisting of nothing but whitespace passes any ASP.NET RegEx Validator, regardless of the regex. This is counter-intuitive. If I set a RegEx validator to use + whitespace-only input should not get through. More interesting is that characters like vertical tab, CR, LF, and form feed are often interpreted as delimiters, so input with nothing but whitespace could in fact do some weird things in a backend app.

Moving along, we see that the input is matched against the supplied patterned. If a match is found and that match begins with the first character of the input and the length of the matched text is the same as the length of the input, then the input passes the validator. Essentially, this is saying that the entire input must match the regex defined. This is good and bad. Its good because I often see developers define a whitelist and forget to use the the "start position" and "end position" characters (^ and $ respectively). Consider an simplified regex for valid email address +@+\.{3}. (This RegEx excludes valid email addresses and is just to illustrate the point). This RegEx matches email addresses like billy@hp.com. It also matches email address like <script>alert("XSS")</script>billy@hp.com because the regex doesn't force that the entire input must match the regex pattern, but merely some subset of the input matches. The RegEx should be ^+@+\.{3}$ where ^ and $ force the entire input to match.

I often see developers make this mistake and in a way Microsoft is solving the problem by saying "Developers are stupid we will also match the entire string so they don't have to." Ok, I like erring on the side of caution, but this prohibits advanced users from using $ and ^ appropriately. Microsoft completely usurps the developer and removes the choice from their hands. You have to use a CustomValidator class to implement RegExs which utilize just ^ or $. However I can live with this because it undoubtedly saves the Internet for silly mistakes.

A final thing that caught my eye was the try ... catch ... block. If the Regex.Match() call throws an exception, the validator returns true indicting the input is safe. This means in event of an error, the validator fails open instead of failing closed! Deciding when applications/appliances/software/hardware/structures should fail open or fail closed is way beyond the scope of this post and the answer is almost always circumstantial based on the individual situations. Quick, should firewalls fail open or closed? Fail open? Well then an attacker knocks out your firewalls and its open seasons on the FTP servers and Samba shares inside your organization. Fail closed? Thats a nifty DoS you built into your network infrastructure now isn't it? when should input validation fail open or fail closed? Again depend, but my gut tells me it should fail closed more often than it fails open.

Ignoring how it should fail is moot if you can't make it fail. How can we make Regex.Match() throw an exception? Null strings would do it, but a brief glance at the code paths in ASP.NET seems to prevent that from occuring. Invalid RegEx syntax would do it, but you get compile-time problems or an immediate runtime error during the Page_Load event, long perform input is validated. So RegEx Validators in ASP.NET fail open if there is an exception, but I can't seem to get it to exception in an exploitable way to sidestep input validation. Perhaps someone else can take a swing at it :-)

Showing results for 
Search instead for 
Do you mean 
About the Author(s)
HP Blog

HP Software Solutions Blog


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation.