Friday, April 1, 2011

Validation in MVC3: An Example

[[Update]]
I had to add a null check to my custom validator. You can find out more information in this post.


Validation is a big aspect of security in web applications.  I can't count how many times I have seen blatant ignorance of this simple fact.  Just recently I was browsing an application built by a 3rd party (who probably charged an arm and a leg for their product).  It took about 1 minute to find a huge sql injection flaw in their application.  One of the get parameters that was being passed into their application was being put directly into a database call.  Worse than this, I got an error message telling me that my sql statement had not worked.  This error message told me the following pieces of information.

1)  It returned the actual sql call
2)  It told me the database that it was using along with the version
3)  It told me the web framework that was being used.

The security industry has spent a lot of time trying to educate developers on best practices for building secure applications.  It is unfortunate to see people still do this kinda of sloppy work.  I guess there is a reason why injection attacks are still on the OWASP top 10 list.

In this article I am going to go over making a custom user name validation attribute.

User names are part of most web applications these days.  I want you to note that there are other perfectly valid ways to do what I am doing here.  I have chosen to make a custom attribute because I assume that I will be using this user name validation in other parts of my application.  Following the DRY principals, it is best to create a custom attribute and go from there.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace MvcApplication2.Attributes
{
    public class UserNameAttribute : ValidationAttribute 
    {
        private const string WHITE_LIST_REGEX = @"^[a-zA-Z0-9]*$";
        private const int MIN_LENGTH = 5;
        private const int MAX_LENGTH = 25;

        public UserNameAttribute()
        {
            // Set a default error message that does not give any information away
            // We don't want an attacker to gain information as to how we build our user names
            // This is a good security measure in cases when the site is not open to the public
            // registration.
            ErrorMessage = "Please enter a valid user name.";
        }

        public override bool IsValid(object value)
        {
            if (value == null)
            {
               return false;
            }
            // Sanity check 1:  Is it a string?
            if (!(value is string))
            {
                return false;
            }

            var userName = value as string;

            // Sanity check 2:  Is it within acceptible norms?
            if (userName.Length < MIN_LENGTH ||
                userName.Length > MAX_LENGTH)
            {
                return false;
            }

            // White List Check
            if (Regex.IsMatch(userName, WHITE_LIST_REGEX))
            {
                return true;
            }

            return false;
        }
    }
}

In order to do proper input validation you have to follow the following rules.

1)  Input is always invalid by default
2)  Input should conform to a known whitelist
3)  Sanity checks should be done to ensure that you only operate on plausible values
4)  Information leakage should be avoided on unauthorized pages

As you can see from the above code, I return false by default.  I use a generic, standard error message to combat (4).  Building white lists are easy with the use of regular expressions.  You can validate almost any type of input.  In the case above, I use a business rule defined in my application to build my white list.  I know that my user names only have letters and numbers.  I can thus enforce this in a white list as shown in the code.  The last check is the one that tests for min and max length.  Although I should enforce this on the form, we all know that the any client side checking can be disabled very easily.  It is easy to build in a check here to make sure that the length of the user name provided meets known business rules.  I could have just as easily incorporated this into the same regular expression that did the character white list.  It would look something like

string fullRegex = @"^[a-zA-Z0-9]{5,15}$";

In this case, my LoginController contains a Login action that takes a LoginModel model.  It is easy to add the above attribute in the LoginModel to provide the necessary protection.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MvcApplication2.Attributes;

namespace MvcApplication2.Models
{
    public class LoginModel
    {
        [Required]
        [UserName]
        public string UserName { get; set; }
    }
}

Of course, security in layers is the best protection to use.  This example above is just one of the layers that you can use to protect your application.  Now that the input has passed some validation, you can use the user name supplied and check against your database to see if the user actually exists.

Happy validating!

No comments:

Post a Comment