Securing your ASP.NET MVC 4 App and the new AllowAnonymous Attribute

2 March 2013 Update: Added security links
20 June 2012 Update: Cookieless Session and Authentication not supported in ASP.NET MVC.

   

Executive Overview

You cannot use routing or web.config files to secure your MVC application. The only supported way to secure your MVC application is to apply the Authorize attribute to each controller and use the new  AllowAnonymous attribute on the login and register actions. Making security decisions based on the current area is a Very Bad Thing and will open your application to vulnerabilities. 

Web.config-based security should never be used in an MVC application. The reason for this is that multiple URLs can potentially hit a controller, and putting these checks in Web.config invariably misses something.

There is a fundamental difference in protected resources between WebForms and MVC.  In WebForms, the resources you're trying to protect are the pages themselves, and since the pages exist on disk at a well-known path you can use Web.config to secure them.  However, in MVC, the resources you're trying to protect are actually controllers and actions, not individual paths and pages. If you try protecting the path rather than the controller, your application likely has a security vulnerability.

In MVC, by default all controllers + actions are accessible to all users, both authenticated and guest. To secure controllers or actions, the Authorize attribute has been provided (which can be applied globally as shown below).

This blog post is not a comprehensive list of security issues on ASP.NET MVC 4, it focuses on how the the AuthorizeAttribute applied in global.asax can be use to ensure all methods are authorized (except those explicitly white listed with the AllowAnonymous attribute. See the security section of the asp.net community site for more information.

ASP.NET MVC 3 introduced global filters, which allows you to add the AuthorizeAttribute filter to the global.asax file to protect every action method of every controller. (In MVC versions prior to MVC 3, it was difficult to enforce the AuthorizeAttribute attribute be applied to all methods except login/register. See my previous blog on security for details.) The code below shows how to add the AuthorizeAttribute filter globally.

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}

The problem with applying Authorize globally is that you have to be logged on (authorized) before you can log on or register.  In a previous blog, I suggested you create a custom AllowAnonymous attribute that you could apply to your login and register actions to explicitly opt out of authorization (that is, override the global application of Authorize on methods/controllers decorated with the attribute).  (Actually, Levi came up with the AllowAnonymous idea.)  ASP.NET MVC 4 includes the new AllowAnonymous attribute, you no longer need to write that code.  Setting the AuthorizeAttribute globally in global.asax and then whitelisting (That is, explicitly decorating the method with the AllowAnonymous attribute)  the methods you want to opt out of authorization is considered a best practice in securing your action methods.  Other approaches such as blacklisting methods (that is, decorating each method you want  with the AuthorizeAttribute attribute ), or creating a base controller with an [Authorize] attribute, and derive each controller (except the Account/Login controller) from that base class – have the problem that new controllers/methods are not automatically protected. See Securing your ASP.NET MVC 3 Application for more details.

When you create a new ASP.NET MVC 4 internet application, you’ll see the login and register methods decorated with this attribute:

  [AllowAnonymous]
 public ActionResult Login(string returnUrl)

 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public ActionResult Login(LoginModel model, string returnUrl)

 [AllowAnonymous]
 public ActionResult Register()

 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public ActionResult Register(RegisterModel model)
 To add a global authorization filter to your Web ApiController,  add the following line to the  
 Application_Start method in the global.asax file:
      GlobalConfiguration.Configuration.Filters.Add(new System.Web.Http.AuthorizeAttribute());

Notice the MVC controller authorize filter is from System.Web.Mvc while the ApiController authorize filter is from System.Web.Http.

Am I Safe Now?

ASP.NET applications configured for forms authentication use an authentication ticket that is transmitted between web server and browser either in a cookie or in a URL query string. The authentication ticket is generated when the user first logs on and it is subsequently used to represent the authenticated user.  It contains a user identifier and often a set of roles to which the user belongs. The browser passes the authentication ticket on all subsequent requests that are part of the same session to the web server. Along with the user identity store, you must protect this ticket to prevent compromise of your authentication mechanism.

Failing to properly protect forms authentication is a common vulnerability that can lead to the following:

  • Elevation of privileges. An attacker could elevate privileges within your application by updating the user name or the list of roles contained in the ticket prior to posting it back to the server. An attacker who can upload malicious code to your application can also successfully create and modify the form’s authentication tickets.

  • Session hijacking. An attacker could capture another user's authentication ticket and use it to access your application. There are a number of ways that this could happen:

    • As a result of a cross-site scripting vulnerability.
    • If the transport is not being protected using a security mechanism such as Secure Sockets Layer (SSL).
    • If the ticket is stored in the browser cache.
  • Session usage after sign-out. Even after the user has logged out of the application and the application has called FormsAuthentication.SignOut, the authentication ticket remains valid until its time-to-live (TTL) expires, so it can be used by an attacker to impersonate another user.

  • Eavesdropping. An attacker could look inside a form’s authentication ticket to obtain any sensitive information it contains and use this information to compromise your application.

  • Compromise of the user identity store. An attacker with access to the user identity store may obtain access to user names and passwords, either directly from the data store or by using a SQL injection attack.

To protect against these threats, you can apply the RequireHttpsAttribute  to the global filters collection in the global.asax file.

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new System.Web.Mvc.AuthorizeAttribute());
    filters.Add(new RequireHttpsAttribute());
}

Many web sites log in via SSL and redirect back to HTTP after you’re logged in, which is absolutely the wrong thing to do.  Your login cookie is just as secret as your username + password, and now you’re sending it in clear-text across the wire.  Besides, you’ve already taken the time to perform the handshake and secure the channel (which is the bulk of what makes HTTPS slower than HTTP) before the MVC pipeline is run, so redirecting back to HTTP after you’re logged in won’t make the current request or future requests much faster.  If you’re hosting static content (youtube for example), change your embedding to use HTTPS rather than HTTP. If you drop down to HTTP from HTTPS without correctly signing out (see https://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.signout.aspx ) your username + password is wide open. It's not enough to call FormsAuthentication.SignOut().

For information on setting up SSL on ASP.NET MVC, see my blog entry Better, Faster, Easier SSL testing for ASP.NET MVC & WebForms. IIS Express (the new default web server for Visual Studio 11) supports SSL.

Over-Posting Model Data

Suppose you have a blog and your comments are represented by this object from your ORM system:

 public class Comment
{
    public int ID { get; set; }      // Primary key
    public int BlogID { get; set; }  // Foreign key
    public Blog Blog { get; set; }   // Foreign entity
    public string Name { get; set; }
    public string Body { get; set; }
    public bool Approved { get; set; }
}

And this is the Create view for the comments to be entered by users:

  <div class="editor-label">
     @Html.LabelFor(model => model.Name)
 </div>
 <div class="editor-field">
     @Html.EditorFor(model => model.Name)
     @Html.ValidationMessageFor(model => model.Name)
 </div>

 <div class="editor-label">
     @Html.LabelFor(model => model.Body)
 </div>
 <div class="editor-field">
     @Html.EditorFor(model => model.Body)
     @Html.ValidationMessageFor(model => model.Body)
 </div>

The intention for comments is that they won't be approved by default. The blog owner will come along later and use the UI to approve all the non-spam comments.

What happens if the bad guy includes "Approved=true" in the form post? The model binder will happily set the Approved property to true. That's definitely not what you intended! It actually gets worse: if the bad guy guesses your entity object is called Blog, he might try posting values into fields like Blog.Body and actually be able to overwrite the body of the blog post. This is a potential disaster!

How can you prevent this? 

  1. The preferred approach is to pass a view model that doesn’t contain any fields you don’t want exposed  ( Blog and Approved in our sample. )
  2. You could use the Bind attribute, either on the Comment type or on the Comment action parameter, to indicate which properties are approved or disapproved for binding.  The "white-list" approach is preferred so that only the named properties can be bound to, rather than the "black-list" approach (which excludes specific properties, and allows binding to everything else). With the black-list approach, you have to remember to black-list new properties added to the model.
  3. Use the (Try)UpdateModel methods on Controller have overloads which accept white-/black-list parameter lists to tell the system which properties are eligible for binding.

Under-Posting Model Data

Under-posting is also a security risk. See Brad Wilson blog entry Input Validation vs. Model Validation in ASP.NET MVC which discusses security issues related to model validation. Although the blog was written in the ASP.NET MVC 2 timeframe, the security details apply to ASP.NET MVC 4 versions.

Mixing Windows Authorization with Forms Authentication

The ASP.NET team doesn’t support using mixed-mode authentication in an application.  If you search the web, you’ll find blog posts on how to do this, but please note that the techniques discussed in these blog posts are discouraged by the team.  The reason this is discouraged is that it is very difficult to reason about security from a correctness point of view, and there are trivial attacks against such a setup that can allow malicious clients to masquerade as an authenticated user. If a supported/secure approach to using mixed-mode authentication is important to you, make a request on our UserVoice page.

Cookieless Session and Authentication

ASP.NET MVC does not support cookieless session and authentication and we actively discourage it. Cookieless  sessions are vulnerable to session hijacking attacks.

Deploy a Secure ASP.NET MVC app with Membership, OAuth, and SQL Database to a Windows Azure Web Site

ASP.NET MVC 4 Security section of content map

Here’s some good StackOverflow (SO) links to Levi’s responses to area security questions.

This blog post is not a comprehensive list of security issues on ASP.NET MVC 4, it focuses on how the the AuthorizeAttribute applied in global.asax can be use to ensure all methods are authorized (except those explicitly white listed with the AllowAnonymous attribute. See the security section of the asp.net community site for more information.

In addition to blogging, I use Twitter to-do quick posts and share links. My Twitter handle is: @RickAndMSFT