.NET MVC Security

Most web security attacks are based on doing unexpected things to user input to fool the website into doing something it shouldn't. As web developers we all know this, but it's hard to keep track of all the exploits that are out there and it's easy to overlook things. The attacks that target the users of a website, work by making them enter unexpected input without them realising it. This often relies on them being logged on to the target site in question so their browser is authenticated when the user clicks on a link in a phishing email.

These notes are about how to avoid making a site developed using ASP.NET MVC vulnerable to these attacks. They are based on this excellent presentation by Scott Hanselman and Phil Haack at Mix 2010.

Disclaimer: This post cover the most common security exploits and how to prevent them. There's no doubt though that people will find more exploits, so you need to stay on your toes and if your project is dealing with sensative data then you should get an expert in to test your site for vulnerabilities.

Sources of user input

The first and most important rule is ‘don't trust user data'. User input can come from many sources: form fields, the URL, an uploaded file. It can also come from the data in a database as this may have been collected from user input and may not have been checked. Particularly if the database holds data that was collected before you were around being so security conscious.

JavaScript Injection

If an attacker can add their own JavaScript to your page, you're in trouble. They can do all manner of evil.

If you've printed user input from a url directly to screen it would be relatively easy to construct a url that contained script tags. To stop this you need to make sure that you encode your input. There are different ways to do this depending on what version of MVC you're using.

  • MVC3 using razor: @userInputValue
    The razor view engine in MVC3 Html encodes everything unless you use @Html.Raw(userInputValue)
  • MVC2 on .NET 4: <%: userInputValue %>
  • MVC1 or MVC2 on .NET 3.5: <%= Html.Encode(userInputValue) %>

All these different syntaxes pass the input through Html.Encode they've just made it easier to do the right thing in newer versions of MVC.

However, this only protects us if the user input is displayed in the HTML. If user input is printed in a JavaScript block then it can be relatively easy to sneak in extra commands.

Take for example this lovely bit of foolish JavaScript code on a .NET MVC web page:

<script type="text/javascript">
    $(function() {
        var code='@ViewBag.input';
        $('#inputBit').hide().html(code).show('slow');
    });
</script>

Now, as this ViewBag.Input is getting run through Html.Encode you're safe from an attacked trying to bung new JavaScript directly into the page, but you're using JQuery to add this to the dom and that opens up a new attack route.

Html.Encode doesn't encode Unicode escape sequences such as \x3c (unicode for <), which would be fine if you were displaying it as HTML, but when this is added to the dom via JavaScript it ends up being the starting part of a tag.

If using jQuery to add text to the dom you can get round this by using .text rather than .html and all will be fine.

Or you can use @Ajax.JavaScriptStringEncode(userValue) when displaying text that is part of a JavaScript block. This will also encode all those extra things that could catch JavaScript out.

Cross site request forgery

This is when a request comes from a different site. This could be something as harmless as a robot posting spam to your blog, or it could be a request to pay money from your banking website. This attack is most harmful when the browser that makes the request is logged into the attacked website. Although a different site is initiating the request the browser is authenticated and the attacked website can't tell the request came from somewhere else.

Decorating your HttpPost action methods with the [ValidateAntiForgeryToken] attribute means they will only accept requests that contain an anti forgery token. You can place an anti forgery token field in a web form using @Html.AntiForgeryToken(). Now only forms that post a valid token will be accepted by your action method. For more details on this you can read Steve Sandersons blog post on CSRF.

Returning sensitive data as JSON

If your site returns JSON and people need to be logged in to see this data you need to be careful. There is a clever way that a page from a different site can get at your secured JSON if the user is currently logged in to your site. This only works if the JSON has an array of objects at its route and only if the data is requested as part of a GET request.

Since MVC 2 JSON results are only available on POST requests as standard. This does go against the RESTFUL purpose of POST, so you can change this behaviour using JsonRequestBehavior.AllowGet when returning your JSON result. If you do, you must make sure that there is an object at the route of your returned JSON. If you need to return an array you could wrap this in an object. If the data is public then you don't need to worry about this loophole.

MVC overposting

Model binding is one of the very clever things about MVC and it can make things very quick and easy. It does leave us vulnerable to one attack though. If we have a form that allows a user to change their profile information. The forms action takes a bound model of a user and then saves this data. What if a clever person figured out that the user model also had a field called HasFullAdminRights. This isn't being sent by our form, but if the user intercepted the form submission and added this with the value true then the model binding would automatically add it to the model and save the user with their ill-gotten super privileges.

There are two ways to stop this. Firstly, you can specify what properties get bound or not bound when the object is passed into the action method, like this:

public ActionResult Add([Bind(Include = "FirstName, LastName")]User newUser)

or

public ActionResult Add([Bind(Exclude = "HasFullAdminRights")]User newUser)

Secondly, you can also include and exclude properties when using UpdateModel and TryUpdateModel, like this:

public ActionResult Update(int id, FormCollection form)
{
    // Update details
    var user = getUser(id);

    if (TryUpdateModel(user, "User", new[] {"FirstName", "LastName"}, new[] {"HasFullAdminRights"}, form))
    {
        // ..

SQL injection

I'm not going to cover this in detail. If you're using MVC and still constructing SQL queries from strings and splicing user input into them, then you're in trouble and the user input could inject nasty SQL into your database. Using an ORM like Entity Framework or NHibernate will prevent these attacks and they're a lot nicer to boot. If you don't want to use an ORM then use stored procedures.

Summary

In summary:

  • Encode all user input shown on a page using: @userInput,
  • Encode user input added to JavaScript using @Ajax.JavaScriptStringEncode(userValue).
  • Use anti forgery tokens.
  • Don't return JSON arrays in GET requests.
  • Use include and exclude properties to protect your models from overposting.
  • Use an ORM

If you know of any other exploits that we can protect ourselves against, or I've missed anything then please let us know in the comments.

Ali said

Nice post, One question that springs to mind thought what if I want to let users add some html. (to create pages in a cms for instance). In MVC I have to add the [ValidateInput(false)] attribute. But is there any way to make this more secure? I guess I could validate the input with a regex or something on the server side.

Richard Garside said

In MVC 3 you have a bit more control over it and can add [AllowHtml] to a property on your model, rather than just the whole of a controller.

This doesn't solve the problem of only allowing safe HTML though. I've been grappling with this recently and not found a solution I'm happy with.

I started with a few simple regex and only allowed a few tags through. But, as my required list of tags grew and the properties I wanted to allow and not allow grew, regex stopped being a nice solution.

For example a closing div tag is not a bad thing, but allowing a user to enter one without an opening one could really mess up a page's layout.

If anyone else has any ideas, or knows of any libraries that tackle this problem I'd be interested in hearing them.