Home > DeveloperSection > Articles > security in asp.net mvc 4

security in asp.net mvc 4


ASP.NET MVC ASP.NET MVC 
Ratings:
0 Comment(s)
 8962  View(s)
Rate this:

 Security in ASP.NET MVC 4

This article discusses the details of how to build secure ASP.NET MVC web applications, including guidance on how to secure web applications, the differences that need to be taken into account when securing Internet, intranet or extranet applications, as well as how to take advantage of functionality built right into the .NET Framework that can help you to prevent the common security issues that most web applications face.

Building Secure Web Applications

Benjamin Franklin once said that "an ounce of prevention is worth a pound of cure". This statement conveys the philosophy that you should embrace when it comes to securing your web applications: the world is a dangerous place and web applications often represent attractive targets for would-be attackers, so you’re going to want to be prepared.

Unfortunately there are no silver bullets when it comes to web application security; it isn’t as simple as including a library or making a method call. Security is something that needs to be baked into an application right from the start and not an afterthought that is tacked on at the last minute.

There are, however, a few security principles that we will explain over the next few sections that can have a great impact creating more secure ASP.NET MVC web applications. If you keep these principles in mind as you design and implement your web applications, you have a much greater chance of avoiding some of the more common and serious security mistakes.

Defense in Depth

Just because a website is the only application layer that directly interacts with the outside world doesn’t mean that it is the only layer responsible for enforcing security. Much to the contrary, secure systems are based on the notion that each application layer and sub-system is responsible for its own security and should act as its own gatekeeper — it is often assumed that a particular layer will only be called from another, trusted layer, however that is not always the case! Instead, each layer should act as if it is always interacting directly with the outside world, authenticating and authorizing users before allowing them to perform any actions.

Never Trust Input

Any input from a user or another system should always be treated as a potential treat so always be sure to validate any input before using it. Don’t ever assume that you can trust the data because it has already been validated elsewhere.

For example, client-side form validation using JavaScript in the browser helps to create a more enjoyable user experience, but this should not be your only line of defense since it is very easy for a would-be attacker to submit a form post directly to the server and bypass any client validation you may have in place. As such, client validation should be considered a convenience feature — not a security feature — and controllers should always validate the data they accept.

Enforce the Principle of Least Privilege

Execute code using an account with the least amount of privilege and design your application not to require elevated rights unless necessary. In scenarios that do require elevated rights, restrict those rights by granting them for as short a period of time as possible: complete the work, and then immediately remove the rights. For example, instead of running an entire web site under an administrator account just to allow disk access to save uploaded files, create a user account that has no direct access to the local machine accept to a specific folder where the account has access to create new files but not to delete, update, or execute them.

Assume External Systems are Insecure

It’s just as important to authenticate and authorize a computer or external application as it is a human end user. When systems need to interact with each other, consider using different system accounts for each external system that your application communicates with, and then restrict that account’s permissions so that it can only access operations the external system needs to perform.

Reduce Surface Area

Avoid exposing information or unnecessary operations. For instance, ASP.NET MVC controllers should minimize the number of actions that they expose as well as restrict those actions' input parameters only to the data that is necessary for the action to do what it needs to.

Note: -

ASP.NET MVC’s model binding BindAttribute provides the Include and Exclude properties that allow you to specify a comma-delimited list of model properties that should be bound or ignored, respectively.

Likewise, log and handle any exceptions that your application generates, making sure to never return system details such as file paths, account names, or database schema information that an attacker could use to take advantage of the system.

Disable Unnecessary Features

The most common attacks are automated attacks that target widely-known vulnerabilities in popular platforms or services. To avoid becoming a target of this type of attack, it is a good idea to reduce your exposure by uninstalling or disabling features or services that your application does not require.

For instance, if your application does not send email you should disable all email on the host machine.

Securing an Application

Web applications often deal with several kinds of users. For example, your application may interact with: end users, who are the primary audience of your application; administrators, who perform tasks related to monitoring and deploying the application; and application or service account "users", which are accounts that are used to communicate between different layers of an application or interact with external services.

At a high-level, the first thing you need to consider when designing an ASP.NET web application is the authentication model your site requires; then you can divide the features of your application into different security authorization roles.

Authentication

Authentication is the process of identifying who is accessing the application. Authentication provides an answer to the following questions: Who is the current user? And Do they represent who they say there are? Both ASP.NET and ASP.NET MVC alike allow you to choose between either Windows Authentication or Forms Authentication.

Authorization

Authorization is the process of determining what level of rights an authenticated user should have for accessing secured resources. The ASP.NET MVC Framework allows you to declaratively add the AuthorizeAttribute to controller actions (or entire controllers) to programmatically check to see if a user is in a specific role.

Once you have defined the security model that your application will use for end users, it is time to decide how the various application layers will communicate with each other.

One of the most popular ways to enable inter-application communication is to create an application service account that is granted the least amount of privileges required for the layers to communicate with each other.

For example, if a web application only requires the ability to search and report on data and not modify it then the service account would only be granted read access to the web server’s local file system and the application’s database.

In cases where read and write access is required, the attack surface can still be minimized by granting the service account very fine-grained access to specific systems and features. For instance, restricting uploads to a single folder and only granting the service account write access to the specific database tables the application needs to update or insert into to.

Securing an Intranet Application

Both intranet and extranet web applications are most often configured to use Windows Authentication. Under this approach, a user’s Windows security token is sent with the HTTP request as they navigate throughout the web application.

The application then uses this token to verify that the user has a valid account on the local machine (or domain) as well as evaluating the roles that the user belongs to in order to validate their ability to perform a given action. When users are not properly authenticated or authorized, they are prompted by web browser to enter their security credentials.

Using the AuthorizeAttribute

The AuthorizeAttribute allows you to declaratively restrict access to a controller action method by rejecting requests whenever a user tries to access a controller action that they don’t have rights to.

Note

If the Visual Studio built-in web server is hosting the application a blank page will be returned since it doesn’t support Windows Authorization failures.

Authorize Attribute Properties

Properties

Description

Order

Defines the order in which the action filter is executed. (Inherited from FilterAttribute)

Roles

Gets or sets the roles required to access the action method.

Users

Gets or sets the user names required to access the action method.

Restricting User Access

 

[Authorize(Users = @"Company\Jess, Company\Todd")]

 

public ActionResult AdminProfile()

{

    return View();

}

 

 

Though this example specifies a list of explicit user names, it is generally a better idea to use Windows Groups instead. This makes application access management significantly easier since the same Windows Group name can be used in multiple places and the members of the Windows Group can be modified using standard Windows tools.

In order to leverage Windows Groups instead of usernames, simple populate the Roles property with the Windows Group names:

Using Windows Groups

[Authorize(Roles = "Admin, AllUsers")]

public ActionResult UserProfile()

{

    return View();

}

 

[Authorize(Roles = "Executive")]

public ActionResult ExecutiveProfile()

{

    return View();

}

Forms Authentication

The limitations of the Windows Authentication approach can become real clear as soon as your application’s user base extends outside of your local domain. In these scenarios — that is, most publicly-accessible Internet sites — you’ll want to use ASP.NET’s Forms Authentication instead.

Using the Forms Authentication approach, ASP.NET issues an encrypted HTTP cookie (or query string value, if the user has cookies disabled) to identify authenticated users across all future requests. This cookie is tightly coupled to the user ASP.NET session such that when the session times out or the user closes their web browser, the session and cookie will become invalid and the user will need to log in again to establish another session.

Note

It’s highly recommended that you use SSL in conjunction with Forms Authentication whenever possible. SSL encryption will automatically encrypt the user’s sensitive credentials which would otherwise be sent to the server in clear, human-readable text.

Since it is by far the most commonly-used authentication technique, most of the ASP.NET MVC web application templates (all except the Intranet template) come pre-configured to use Forms Authentication.

ASP.NET MVC’s Internet application template even goes so far as to provide a default implementation right out of the box, generating a controller class named AccountController and its associated login and new user registration views.

Account Controller

The AccountController comes fully equipped for supporting typical ASP.NET form-based authentication scenarios. Out of the box the class offers the ability for new users to register with the site, as well as authenticating existing users and even includes logic that lets users change their password.

Under the covers the controller uses the standard ASP.NET Membership and Roles providers as configured in the application’s web.config. The default ASP.NET Membership Provider configuration stores the site’s membership data in a SQL Express database called ASPNETDB.MDF located under the application’s /App_Data folder. Since SQL Express is generally only suitable for development scenarios, however, you will probably want to switch to a more production-ready data store such as the full version of Microsoft SQL Server or even Active Directory. Alternatively, you can create and register your own custom providers in order to extend the capabilities of the membership provider to include additional fields or to provide additional features that the default providers do not offer.

Luckily, changing the settings for the default Membership Provider or even switching to a custom membership provider is only a matter of configuration. To change the Membership Provider configuration, simply update the <membership> section of the application’s web.config:

Setting up the Membership Provider in Web.Config

<membership defaultProvider="DefaultMembershipProvider">

  <providers>

    <add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

         connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true"

         requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5"

         minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />

  </providers>

</membership>

 

Authenticating Users

When a user tries to access a secure section of a web application they will be redirected to the login form. The Account Controller leverages the AllowAnonymousAttribute to indicate that the Login action is an exception to the authorization rules and that unauthenticated users may access it. If this were not the case, users would never be able to see the login form in order to authenticate them!

Since the Login controller action is accessible both by standard HTML Login form or AJAX based form, the action looks at the content property of the querystring to determine which one of these it is being called from. When the content parameter is not null, the action returns the AJAX version of the login form; otherwise it is assumed that the request is a standard full-page browser request and action returns the full login form view.

User Login

 

[AllowAnonymous]

public ActionResult Login()

{

    return ContextDependentView();

}

 

private ActionResult ContextDependentView()

{

    string actionName = ControllerContext.RouteData.GetRequiredString("action");

    if (Request.QueryString["content"] != null)

    {

        ViewBag.FormAction = "Json" + actionName;

        return PartialView();

    }

    else

    {

        ViewBag.FormAction = actionName;

        return View();

    }

}

 

After the user enters in their security credential and hits submit the login form posts the credentials back to the [HttpPost] version of the Login action in order to try to authenticate the user.

Authenticating users using the ASP.NET Form Authentication is a two-step process:

  1. First, the Login action calls the Membership.ValidateUser method to see if the user is valid.
  2. Then, if the Membership Provider says that the credentials are valid, the action calls FormsAuthentication.SetAuthCookie to create the user’s security token.

Finally, if the user is successfully logged in they will be redirected to the URL that originally failed authentication or, if they navigated directly to the login page, they are returned to the application’s home page. If an error occurs during authentication, the user is returned back to the login form to try again.

Authenticating the User

[AllowAnonymous]

[HttpPost]

public ActionResult Login(LoginModel model, string returnUrl)

{

    if (ModelState.IsValid)

    {

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

        {

            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

            if (Url.IsLocalUrl(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);

}

Registering New Users

Before users are able to authenticate themselves with the site, they must first have an account. Though it is possible for website administrators to manage user accounts manually, the much more common approach is to allow users to register their own accounts. In the ASP.NET MVC Internet template, it’s the Register controller action’s job to interact with the user and collect everything necessary to create a new user account with the Membership Provider.

At a glance, the Register action looks a lot like the Login action you saw previously — allowing users to access the action via the [AllowAnonymous] attribute and leveraging context-specific logic to return a partial view or full view depending on whether the request is an AJAX request or not.

Instead of authenticating users, however, form posts to the Register action tell the application to register a new user with the ASP.NET Membership Provider using its Membership.CreateUser() API method.

When the new user is successfully registered, the action uses the same FormsAuthentication.SetAuthCookie() method shown in the Login action to automatically authenticate the new user, then redirects them to the application’s home page.

Registering New Users

[AllowAnonymous]

public ActionResult Register()

{

    return ContextDependentView();

}

 

[AllowAnonymous]

[HttpPost]

public ActionResult Register(RegisterModel model)

{

    if (ModelState.IsValid)

    {

        // Attempt to register the user

        MembershipCreateStatus createStatus;

        Membership.CreateUser(model.UserName, model.Password, model.Email, passwordQuestion: null, passwordAnswer: null, isApproved: true, providerUserKey: null, status: out createStatus);

 

        if (createStatus == MembershipCreateStatus.Success)

        {

            FormsAuthentication.SetAuthCookie(model.UserName, createPersistentCookie: false);

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

        }

        else

        {

            ModelState.AddModelError("", ErrorCodeToString(createStatus));

        }

    }

 

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

    return View(model);

}

Changing Passwords

The AccountController provides one additional action that is typical in most Forms Authentication web applications: ChangePassword.

The process begins with a request to the ChangePassword action from a user to change their password, during which the controller attempts to locate the user’s account using the Membership.GetUser API to get an instance of MembershipUser which contains the user’s authentication information.

If it successfully locates the user’s account, the ChangePassword action then calls the ChangePassword method on the MembershipUser, passing in the user’s new password.

After the password is successfully changed, the user is redirected to the ChangePasswordSuccess view, confirming to the user that everything went well and their password is now changed.

Changing the User’s Password

public ActionResult ChangePassword()

{

    return View();

}

 

[HttpPost]

public ActionResult ChangePassword(ChangePasswordModel model)

{

    if (ModelState.IsValid)

    {

 

        // ChangePassword will throw an exception rather

        // than return false in certain failure scenarios.

        bool changePasswordSucceeded;

        try

        {

            MembershipUser currentUser = Membership.GetUser(User.Identity.Name, userIsOnline: true);

            changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);

        }

        catch (Exception)

        {

            changePasswordSucceeded = false;

        }

 

        if (changePasswordSucceeded)

        {

            return RedirectToAction("ChangePasswordSuccess");

        }

        else

        {

            ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");

        }

    }

 

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

    return View(model);

}

Interacting via AJAX

In addition to the standard HTML GET/POST model, the AccountController also supports logging in and registering users via AJAX. Authenticating Users via AJAX shows the AJAX methods for these features.

If you think that these methods look familiar, you’re right — the only major difference between the AJAX-based actions and the GET-based actions is that the controller returns a JSON response (via a JSONResult) rather than an HTML response (via a ViewResult).

Authenticating Users via AJAX

User Authorization

User authorization works the same in Forms Authentication as it does with Window Authentication: placing the AuthorizeAttribute on a controller action method restrict the action only to authenticated users.

When a non-authenticated user attempts to access the controller action ASP.NET MVC will reject their request by redirecting them to the login page instead.

When this happens the original request page is passed as a parameter (ReturnUrl) to the login page, e.g. /Account/LogOn?ReturnUrl=%2fProduct%2fCreateNew. Then, after the user successfully logs in, they will then be redirected back to the original requested view.

Authorizing Users

[Authorize]

public ActionResult About()

{

        return View();

}

When working with Forms Authentication certain pages such as the application’s home or contact us page may be accessible to all users. The [AllowAnonymous] attribute grants access to non-authenticated users.

Allowing Anonymous Access

[AllowAnonymous]

public ActionResult Register()

{

     return ContextDependentView();

}

In addition to declaratively authorizing the user using the User or Groups properties of the AuthorizeAttribute, you may also access the logged in user directly by calling the User.Identity.Name or User.IsInRole to check if the user is authorized to perform a given action.

Programmatically Authorizing Users

[HttpPost]

[Authorize]

public ActionResult Details(int id)

{

    Model model = new Model();

 

    if (!model.IsOwner(User.Identity.Name))

        return View("InvalidOwner");

 

    return View();

}

Guarding Against Attacks

Managing user security is just the first step in securing a web application. The second, more important challenge is guarding against attacks from potential intruders.

While most users do not typically go around trying to hack the application they use, people using your application are generally very good at discovering bugs that can lead to security holes. What’s more, intruders come in many forms: from a simple attacker out to have some fun, to sophisticated automated attacks that leverage worms and viruses designed to attack known vulnerabilities.

Regardless of the type of intruder, the most important weapons that you have at your disposal to defend against attacks are planning and the security principles discussed in this chapter. It is also important that proper monitoring and auditing is utilize so if an attack occurs the operation and development teams can identify it’s cause and guard against future attacks.

The next few sections discuss the most common types of web application attacks and what steps you can take to protect yourself against them.

SQL Injection

A SQL injection attack occurs when an attacker tricks the web application into accepting parameters that that cause an unintended query that uses untrusted data to be run. For demonstration purposes the examples given are simple and straight forward. Just keep in mind that attackers generally do not leverage simple logic; instead, they try to create complex algorithms that identify exploits and then attack those exploits.

Cross-Site Scripting

Like SQL Injection attacks, Cross Site Scripting (XSS) attacks represent a serious threat to web applications that accept input from users.

The root cause of these types of attacks is insufficient validation of input data. These attacks usually occur when the attacker is able to trick the user into viewing fake pages that look similar to the target web application or using innocent-looking emails with embedded links that take a user to unexpected location.

Web applications that contain sensitive data are highly acceptable to XSS attacks, attackers try to hijack cookies that maybe contain user login or session ids. An attack can use these cookies to try to get access to the user’s information or trick the user into doing something harmful such as submitting extra HTML content or malicious JavaScript.

Fortunately, Microsoft recognizes the threat of cross-site scripting and built basic protection right into the Framework in the form of request validation. When ASP.NET receives a request it will examine it to look for markup or scripts submitted in the request (such as form field values, headers, cookies, etc.) and if suspicious content is detected ASP.NET rejects the request by throwing an exception. In addition, the popular Microsoft XSS library has been included in ASP.NET 4.5.

In some scenarios applications such as Content Management Systems (CMS), Forums, and Blogs need to support the input of HTML content. To support this ASP.NET 4.5 introduces the ability to use deferred (or "lazy") request validation and methods for accessing unvalidated request data. It is important to use these features with caution and remember that when you do so, you assume the responsibility of validating input on your own.

To configure ASP.NET to use deferred request validation, update the httpRuntime > requestValidationMode attribute in web.config to 4.5.

<httpRuntime requestValidationMode="4.5" />

When deferred request validation is enabled the validation process will get triggered the first time the application calls the request collection, e.g. Request.Form["post_content"]. To skip the input validation, use the HttpRequest.Unvalidated() helper method to access an unvalidated collection:

using System.Web.Helpers;

 

var data = HttpContext.Request.Unvalidated().Form["post_content"];

Microsoft has included a portion of the popular Microsoft Anti-XSS Library in ASP.NET 4.5. The encoding features are part of the AntiXSSEncoded class which is in the System.Web.Security.AntiXss namespace. The library can be used directly by calling one of the static encoding methods in the AntiXSSEncoded class.

An easy way to utilize the new anti-XSS functionality is to setup an ASP.NET web application to use the class by default. This is done by setting the enoderType in web.config to the AntiXssEncoded. When this is turned on all output encoding will automatically use the new XSS encoding functionality.

<httpRuntime ...

  encoderType="System.Web.Security.AntiXss.AntiXssEncoder,System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

Here are the features from the Anti-XSS library included in ASP.NET 4.5:

  • HtmlEncode, HtmlFormUrlEncode, and HtmlAttributeEncode
  • XmlAttributeEncode and XmlEncode
  • UrlEncode and UrlPathEncode (new to ASP.NET 4.5!)
  • CssEncode

Cross-Site Request Forgery

The web is not a safe place and no matter how secure you try to make your applications there will always be someone who tries to get around the restrictions you put in. While Cross-Site Scripting (XSS) and SQL Injection attacks get a lot of attention, there is another — potentially even more serious — issue that a lot of web developers overlook: Cross-Site Request Forgery (CSRF).

The reason CSRF is such a potential security risk is it can be easily exploit by how the web is meant to work. Here is an example of a controller that is acceptable to a CSRF attack. Everything looks standard forward and fairly innocent, but the controller is a prime target for a CSRF attack.

Controller that can be exploited

 

public class ProductController : Controller

{

    public ViewResult Details()

    {

        return View();

    }

 

    public ViewResult Update()

    {

        Product product = DbContext.GetProduct();

 

        product.ProductId = Request.Form["ProductId"];

        product.Name = Request.Form["Name"];

        SaveUProduct(product);

 

        return View();

    }

}

To exploit the controller, all a resourceful attacker needs to do is set up a page that targets it. Once they persuade a user to visit their page it will try to post to the controller. If the user has already been authenticated using either Windows Authentication or Form Based Authentication then the controller will be oblivious to the CSRF attack.

Example CSRF Attack

<body onload="document.getElementById('productForm').submit()">

    <form id=" productForm'" action="http://.../Product/Update" method="post">

        <input name="ProductId" value="123456" />

        <input name="Name" value="My Awesome Hack" />

    </form>

</body>

So, what’s the cure to this potentially serious security risk?

There are two main ways of blocking a CSRF attack:

 ·          Domain Referrer: check to see if the incoming request has a referrer header for your domain. This will help prevent requests submitted from external 3rd party sources. This approach has a couple of drawbacks: A user can disable sending a referrer header for privacy reasons and attackers can spoof the header if the user’s has a certain version of Adobe Flash installed.

 ·         User Generator Token: Using a hidden HTML field, store a user specific token (e.g. generated from your server) and verify the submitted token is valid. The generated token can be stored in the user session or in a HTTP Cookie.

Using ASP.NET MVC to Avoid Cross-Site Request Forgery

ASP.NET MVC includes a set of helpers that help you detect and block CSRF attacks by creating a user specific token that is passed between the view and the controller and verified each request. And, all you need to do to take advantage of this functionality is to use the @Html.AntiForgeryToken() HTML helper to add a hidden HTML field to your page that the controller will verify on each request.

Note

For increased security, the HTML helper also accepts a salted key that will be used to increase the randomization of the generated token.

Using the AntiForgeryToken Helper

@Html.AntiForgeryToken()

@Html.AntiForgeryToken("somerandomsalt")

For this anti-forgery approach to work, the controller action that handles the form post needs to be aware that the form contains an anti-forgery token by applying the ValidateAntiForgeryTokenAttribute to it.

This attribute will verify that the incoming request includes both a cookie value and form field named RequestVerificationToken, and that both of their values match.

Securing a controller against CSRF

[ValidateAntiForgeryToken]

public ViewResult Update()

{

}

The Anti-CSRF Helpers included with ASP.NET MVC are very useful but also have some limitations that you need to keep in mind as you use them:

 ·          Legitimate users must accept cookies. If the user has cookies disabled in their web browser the ValidateAntiForgeryTokenAttribute filter will reject their requests.

 ·         This method only works with POST requests, not GET requests. In reality, this is not a big deal because you should be only using GET requests for read-only operations.

 ·         If you have any Cross-Site Scripting (XSS) holes in your domain it’s easy for an attacker to access and read the anti-forgery token.

 ·         Out-of-the-Box Web Frameworks like JQuery do not automatically pass the required cookie and hidden HTML field when making AJAX requests. You have to build your own solution for passing and reading the Anti-CSRF token.

Summary

This chapter outlined how to build secure ASP.NET MVC web applications. Including the differences between using Windows and Forms Based Authentication, how to use the AuthorizeAttribute to authorize different users and groups, along with how to guard against SQL injection, Cross-Site Scripting and how to use the CSRF Anti-Forgery Helper.


Don't want to miss updates? Please click the below button!

Follow MindStick