January 2012

Volume 27 Number 01

ASP.NET Security - Securing Your ASP.NET Applications

By Adam Tuliper | January 2012

In the previous issue, I discussed the importance of building security into your Web applications and looked at some types of attacks, including SQL injection and parameter tampering, and how to prevent them (msdn.microsoft.com/magazine/hh580736). In this article, I’ll delve into two more common attacks to help round out our arsenal of application protections—cross-site scripting (XSS) and cross-site request forgery (CSRF).

You might be wondering: Why not just use a production security scanner? Scanners are great tools for finding low-hanging fruit, and they’re especially good at finding application and system configuration issues, but they can’t know your application the way you do. Therefore, it’s imperative that you become familiar with potential security issues and take the time to check your applications and build security into your software development lifecycle.

Cross-Site Scripting

What Is It? XSS is an attack in which script is maliciously injected into a user’s browsing session, generally without the user’s knowledge. These types of attacks have become quite familiar to many people because of incidents in which a large social networking site was struck and its users had messages posted they didn’t authorize. If an attacker posts malicious script that he can cause the browser to execute, this script is executed in the context of the victim’s session, essentially enabling the attacker to do anything he wants to the DOM—including showing fake login dialogs or stealing cookies. Such an attack could even install an HTML key logger on the current page to continually send input from that window to a remote site.

How Is It Exploited? XSS is exploited via several methods, all of which rely on having unescaped or improperly escaped output. Let’s take the case of an application that needs to display a simple status message to the end user. Typically this message is passed on the query string as shown in Figure 1.

Query String Message
Figure 1 Query String Message

This technique is commonly used after a redirect to show the user some sort of status, such as the Profile Saved message in Figure 1. The message is read from the query string and written right out to the page. If the output is not HTML-encoded, anyone can easily inject JavaScript in place of the status message. This type of attack is considered a reflected XSS attack, because whatever is on the query string gets rendered right back to the page. In a persistent attack the malicious script is stored, usually in a database or cookie.

In Figure 1 you can see this URI takes a msg parameter. The Web page for this URI would contain code such as the following to simply write the variable to the page with no encoding:

<div class="messages"> <%=Request.QueryString["msg"]%></div>

If you replace “Profile Saved” with the script shown in Figure 2, the alert function will pop up in the browser from the script included on the query string. The key to the exploit here is that the results are not HTML-encoded and therefore the <script> tag is actually parsed as valid JavaScript by the browser and executed. This is clearly not what the developer here had in mind.

Injecting Script into the URI
Figure 2 Injecting Script into the URI

So an alert pops up—what’s the big deal? Let’s take this example a bit further, as shown in Figure 3. Note that I’ve shortened the attack here to make the point; this syntax isn’t exactly correct but a slight modification would turn this into a real attack.

Creating a Malicious Attack
Figure 3 Creating a Malicious Attack

An attack like this would cause a fake login dialog to be shown to users, where upon they would blithely enter their credentials. In this case, a remote script is downloaded, which is generally allowed by the default browser security settings. This type of attack can in theory happen anywhere a string that hasn’t been encoded or sanitized can be echoed back to a user.

Figure 4 shows an attack using similar script on a development site that allows users to leave comments about its products. Instead of leaving a real product review, however, someone entered malicious JavaScript as a comment. This script now displays a login dialog to every single user reaching the Web page and collects and sends the credentials to a remote site. This is a persistent XSS attack; the script is stored in the database and repeated for everyone who visits the page.

A Persistent XSS Attack Showing a Fake Dialog
Figure 4 A Persistent XSS Attack Showing a Fake Dialog

Another way XSS is exploited is by using HTML elements, as when dynamic text is allowed in HTML tags, like so:

<img onmouseover=alert([user supplied text])>

If an attacker injects text such as “onmouseout=alert(docu­ment.cookie),” this creates the following tag in the browser that accesses the cookie:

<img onmouseover=alert(1) onmouseout=alert(document.cookie) >

There’s no “<script>” tag to potentially filter input on and nothing to escape, but this is a completely valid piece of JavaScript that can read a cookie—potentially an authentication cookie. There are case-specific ways to make this safer, but because of the risk, it’s best not to allow any user input from reaching this inline code here.

How Do You Prevent XSS? Following these rules strictly will help prevent most if not all XSS attacks in your application:

  1. Ensure all of your output is HTML-encoded.

  2. Don’t allow user-supplied text to end up in any HTML element attribute string.

  3. Prevent the use of Internet Explorer 6 by your application by checking Request.Browser as outlined at msdn.microsoft.com/library/3yekbd5b.

  4. Know your control’s behavior and whether it HTML encodes its output. If it doesn’t, encode the data going to the control.

  5. Use the Microsoft Anti-Cross Site Scripting Library (AntiXSS) and set it as your default HTML encoder.

  6. Use the AntiXSS Sanitizer object (this library is a separate download and is addressed later in this article) to call GetSafeHtml or GetSafeHtmlFragment before saving HTML data to the database; don’t encode the data before saving.

  7. For Web Forms, don’t set EnableRequestValidation=false in your Web pages. Unfortunately, most user group postings on the Web advise disabling this setting if there’s an error. The setting is there for a reason and will stop the request if the character combination “<X,” for example, is posted back to the server. If your controls are posting HTML back to the server and receiving the error shown in Figure 5, you should ideally encode the data before you post it to the server. This is a common scenario with WYSIWYG controls, and most modern versions will properly encode their HTML data before posting back to the server.

    Server Error from Unencoded HTML
    Figure 5 Server Error from Unencoded HTML

  8. For ASP.NET MVC 3 applications, when you need to post HTML back to your model, don’t use ValidateInput(false) to turn off Request Validation. Simply add [AllowHtml] to your model property, like so:

public class BlogEntry
{
  public int UserId {get;set;}
  [AllowHtml]
  public string BlogText {get;set;}
}

Some products try to detect <script> and other word combinations or regular expression patterns in a string to try to detect XSS. These products can provide additional checks but aren’t completely reliable because of the many variants attackers have created. Take a look at the XSS Cheat Sheet at ha.ckers.org/xss.html to see how difficult detection can be.

To understand the fixes, let’s suppose an attacker has injected some script that ended up in a variable in our application from either the query string or a form field as shown here:

string message = Request.QueryString["msg"];

or:

string message = txtMessage.Text;

Note that even though a TextBox control HTML encodes its output, it doesn’t encode its Text property when you read it in from code. With either of these lines of code, you’re left with the following string in the message variable:

message = "<script>alert('bip')</script>"

In a Web page containing code similar to the following, the JavaScript will be executed in the user’s browser simply because this text was written to the page:

<%=message %>

HTML encoding the output stops this attack in its tracks. Figure6 shows the main options for encoding the dangerous data.

These options prevent the kind of attack shown in the example and should be used in your applications.

Figure 6 HTML-Encoding Options

ASP.NET (Either MVC or Web Forms) <%=Server.HtmlEncode(message) %>
Web Forms (ASP.NET 4 syntax) <%: message %>
ASP.NET MVC 3 Razor @message
Data Binding

Unfortunately, the data-binding syntax doesn’t yet contain a built-in encoding syntax; it’s coming in the next version of ASP.NET as <%#: %>. Until then, use:

<%# Server.HtmlEncode(Eval("PropertyName")) %>

Better Encoding

From the AntiXSS Library in the Microsoft.Security.Application namespace:

Encoder.HtmlEncode(message)

It’s important to know your controls. Which controls HTML encode your data for you and which controls don’t? For example, a TextBox control does HTML encode the rendered output, but a LiteralControl doesn’t. This is an important distinction. A textbox assigned:

yourTextBoxControl.Text = "Test <script>alert('bip')</script>";

correctly renders the text to the page as:

Test &lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;

In contrast:

yourLiteralControl.Text = "Test <script>alert('bip')</script>";

causes a JavaScript alert to be displayed on the page, confirming XSS vulnerability. The fix here is simply:

yourLiteralControl.Text = Server.HtmlEncode(
    "Test <script>alert('bip')</script>");

This is a bit trickier when using data binding in Web Forms. Look at the following example:

<asp:Repeater ID="Repeater1" runat="server">
    <ItemTemplate>
      <asp:TextBox ID="txtYourField" Text='<%# Bind("YourField") %>'
        runat="server"></asp:TextBox>
    </ItemTemplate>
  </asp:Repeater>

Is this vulnerable? No, it’s not. Even though the inline code seems as if it could write out the script or break out of the control quotes, it’s indeed encoded. 

What about this:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%# Eval("YourField") %>
  </ItemTemplate>
</asp:Repeater>

Is it vulnerable? Yes, it is. The data-binding syntax <%# %> doesn’t HTML encode. Here’s the fix:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%#Server.HtmlEncode((string)Eval("YourText"))%>
  </ItemTemplate>
</asp:Repeater>

Be aware that if you use Bind in this scenario you can’t wrap a Server.HtmlEncode around it because of how Bind compiles behind the scenes as two separate calls. This will fail:

<asp:Repeater ID="Repeater2" runat="server">
  <ItemTemplate>
    <%#Server.HtmlEncode((string)Bind("YourText"))%>
  </ItemTemplate>
</asp:Repeater>

If you use Bind and aren’t assigning the text to a control that HTML encodes (such as the TextBox control), consider using Eval instead so you can wrap the call to Server.HtmlEncode as in the previous example.

The same concept of data binding doesn’t exist in ASP.NET MVC, so you need to know if the HTML helpers will encode. The helpers for labels and textboxes do HTML encode. For example, this code:

@Html.TextBox("customerName", "<script>alert('bip')</script>")
@Html.Label("<script>alert('bip')</script>")

renders as:

<input id="customerName" name="customerName" type="text"
  value="&lt;script>alert(&#39;bip&#39;)&lt;/script>" />
<label for="">&lt;script&gt;alert(&#39;bip&#39;)&lt;/script&gt;</label>

I mentioned the AntiXSS earlier. Currently at version 4.1 beta 1, the AntiXSS Library has gone through a very nice rewrite and, as far as security is concerned, provides a better HTML encoder than the one that comes with ASP.NET. It’s not that there’s anything wrong with Server.HtmlEncode, but its focus is compatibility, not security. AntiXSS uses a different approach to encode. You can read more about it at msdn.microsoft.com/security/aa973814.

The beta is available at bit.ly/gMcB5K. Do check to see if AntiXSS is out of beta yet. If not, you’ll need to download the code and compile it. Jon Galloway has an excellent post on this at bit.ly/lGpKWX.

To use the AntiXSS encoder, you can simply make the following call:

<%@ Import Namespace="Microsoft.Security.Application" %>
...
...
<%= Encoder.HtmlEncode(plainText)%>

ASP.NET MVC 4 added a great new feature that lets you override the default ASP HTML encoder, and you can use the AntiXSS encoder in its place. As of this writing, you need version 4.1; because it’s currently in beta, you must download the code, compile it and add the library as a reference to your application—which takes all of five minutes. Then, in your web.config, add the following line in the <system.web> section:

<httpRuntime encoderType=
  "Microsoft.Security.Application.AntiXssEncoder, AntiXssLibrary"/>

Now, any HTML-encoding call made through any of the syntaxes listed in Figure 6, including the ASP.NET MVC 3 Razor syntax, will get encoded by the AntiXSS library. How’s that for a pluggable feature?

This library also includes a Sanitizer object that can be used to cleanse HTML before storing it to a database, which is very useful if you provide a WYSIWYG editor to the user for editing HTML. This call attempts to remove script from the string:

using Microsoft.Security.Application;
...
...
string wysiwygData = "before <script>alert('bip ')</script> after ";
string cleanData = Sanitizer.GetSafeHtmlFragment(wysiwygData);
This results in the following cleaned string that can then be saved to the database:
cleanData = "before  after ";

Cross-Site Request Forgery (CSRF)

What Is It? Cross-site request forgery, or CSRF (pronounced sea-surf), is an attack that occurs when someone takes advantage of the trust between your browser and a Web site to execute a command using the innocent user’s session. This attack is a bit more difficult to imagine without seeing the details, so let’s get right to it.

How Is It Exploited? Suppose John is authenticated as an admin­istrator on the PureShoppingHeaven site. PureShoppingHeaven has a URL that’s restricted to admin access and allows information to be passed on the URL to execute an action, such as creating a new user, as shown in Figure 7.

Passing Information on the URL
Figure 7 Passing Information on the URL

If an attacker can get John to request this URL through any of a variety of methods, his browser will request it from the server and send over whatever authentication information might already be cached or in use in John’s browser, such as authentication cookies or other authentication tokens, including Windows Authentication.

This is a simple example, but CSRF attacks can be far more sophisticated and can incorporate form POSTs in addition to GET requests and can take advantage of other attacks such as XSS at the same time.

Suppose John visits a vulnerable social networking site that has been exploited. Perhaps an attacker has placed a bit of JavaScript on the page via an XSS vulnerability that now requests the AddUser.aspx URL under John’s session. This dump from Fiddler (fiddler2.com) after John visits the Web page shows the browser also is sending over a custom site-authentication cookie:

GET https://pureshoppingheaven/AddUser.aspx?userName=hacked&pwd=secret HTTP/1.1
Host: pureshoppingheaven
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Cookie: CUSTOMERAUTHCOOKIE=a465bc0b-e1e2-4052-8292-484d884229ab

This all happens without John knowing it. What’s important to understand is that by design the browser will send over any valid cookies or authentication information. Have you ever noticed that your e-mail client generally doesn’t load images by default? One reason is to prevent CSRF. If you received an HTML-formatted e-mail with an embedded image tag such as the following, that URL would be requested and the server would execute that action if you’re authenticated to that Web site:

<img src='yoursite/createuser.aspx?id=hacked&pwd=hacked' />

If you happened to be an admin on “yoursite” who’s already authenticated, the browser would happily send over that GET request along with any credentials. The server sees this as a valid request by an authenticated user, and it will execute that request without you ever knowing, because there’s no valid image response to be rendered in your e-mail client.

How Do You Prevent CSRF? To prevent CSRF, you start by following certain rules:

  1. Ensure that a request can’t be replayed by simply clicking on a link for a GET request. The HTTP spec for GET requests implies GET requests should only be used for retrieval, not state modification.
  2. Ensure that a request can’t be replayed if an attacker has used JavaScript to mimic a form POST request.
  3. Prevent any actions via GET. For example, don’t allow records to be created or deleted via a URL. Ideally, these should require some user interaction. While this doesn’t prevent a smarter form-based attack, it limits a host of easier attacks, such as the kind described in the e-mail image example as well as basic links embedded in XSS-compromised sites.

Preventing attacks via Web Forms is handled a bit differently from ASP.NET MVC. With Web Forms, the ViewState MAC attribute can be signed, which helps protect against forgery as long as you don’t set EnableViewStateMac=false. You also want to sign your ViewState with the current user session and prevent the ViewState from being passed in on the query string to block what some refer to as a one-click attack (see Figure 8).

Figure 8 Preventing a One-Click Attack

void Page_Init(object sender, EventArgs e)
{
  if (Session.IsNewSession)
  {
    // Force session to be created;
    // otherwise the session ID changes on every request.
    Session["ForceSession"] = DateTime.Now;
  }
  // 'Sign' the viewstate with the current session.
  this.ViewStateUserKey = Session.SessionID;
  if (Page.EnableViewState)
  {
    // Make sure ViewState wasn't passed on the querystring.
    // This helps prevent one-click attacks.
    if (!string.IsNullOrEmpty(Request.Params["__VIEWSTATE"]) &&
      string.IsNullOrEmpty(Request.Form["__VIEWSTATE"]))
    {
      throw new Exception("Viewstate existed, but not on the form.");
    }
  }
}

The reason I assign a random session value here is to make sure the session is established. You could use any temporary session identifier, but the ASP.NET session ID will change on every single request until you actually create a session. You can’t have the session ID changing with every request here, so you have to pin it down by creating the new session.

ASP.NET MVC contains its own set of built-in helpers that protect against CSRF using unique tokens passed in with the request. The helpers use not only a required hidden form field but also a cookie value, making it quite a bit more difficult to forge a request. These protections are easy to implement and absolutely essential to incorporate into your applications. To add @Html.AntiForgery­Token() inside <form> in your view, do this:

@using (Html.BeginForm())
{
  @Html.AntiForgeryToken();
  @Html.EditorForModel();
  <input type="submit" value="Submit" />
}
Decorate any controllers that accept post data with the [Validate­AntiForgeryToken], like so:
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult Index(User user)
{
  ...
}

Understanding Vulnerability

This article looked at cross-site scripting and cross-site request forgery, two common ways Web applications are hacked. Added to the two covered last month, SQL injection and parameter tampering, you now have a good understanding of how applications can be vulnerable.

You’ve also seen how easy it is to build security into your applications to protect against some of the most common attacks. If you’ve already been building security into your software development lifecycle, great! If you haven’t, there’s no better time to start than now. You can audit your existing applications on a per-page/per-module basis and in most cases refactor them very easily. And protect your applications with SSL to prevent sniffing of your credentials. Remember to think about security before, during and after development.


Adam Tuliper is a software architect with Cegedim and has been developing software for more than 20 years. He’s a national INETA Community Speaker, and regularly speaks at conferences and .NET user groups. Check him out on Twitter at twitter.com/AdamTuliper, on his blog at completedevelopment.blogspot.com or at the new secure-coding.com Web site. For more in-depth information on hack-proofing your ASP.NET applications, see his upcoming Pluralsight video series.

Thanks to the following technical expert for reviewing this article: Barry Dorrans