blog post image
Andrew Lock avatar

Andrew Lock

~6 min read

Preventing mass assignment or over posting in ASP.NET Core

Mass assignment, also known as over-posting, is an attack used on websites that involve some sort of model-binding to a request. It is used to set values on the server that a developer did not expect to be set. This is a well known attack now, and has been discussed many times before, (it was a famous attack used against GitHub some years ago), but I wanted to go over some of the ways to prevent falling victim to it in your ASP.NET Core applications.

How does it work?

Mass assignment typically occurs during model binding as part of MVC. A simple example would be where you have a form on your website in which you are editing some data. You also have some properties on your model which are not editable as part of the form, but instead are used to control the display of the form, or may not be used at all.

For example, consider this simple model:

public class UserModel
{
    public string Name { get; set; }
    public bool IsAdmin { get; set; }
}

It has two properties, but we only actually going to allow the user to edit the Name property - the IsAdmin property is just used to control the markup they see:

@model UserModel

<form asp-action="Vulnerable" asp-Controller="Home">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input class="form-control" type="TextBox" asp-for="Name" />
    </div>
    <div class="form-group">
        @if (Model.IsAdmin)
        {
            <i>You are an admin</i>
        }
        else
        {
            <i>You are a standard user</i>
        }
    </div>
    <button class="btn btn-sm" type="submit">Submit</button>
</form>

So the idea here is that you only render a single input tag to the markup, but you post this to a method that uses the same model as you used for rendering:

[HttpPost]
public IActionResult Vulnerable(UserModel model)
{
    return View("Index", model);
}

This might seem OK - in the normal browser flow, a user can only edit the Name field. When they submit the form, only the Name field will be sent to the server. When model binding occurs on the model parameter, the IsAdmin field will be unset, and the Name will have the correct value:

Normal post

However, with a simple bit of HTML manipulation, or by using Postman/Fiddler , a malicious user can set the IsAdmin field to true. The model binder will dutifully bind the value, and you have just fallen victim to mass assignment/over posting:

Malicious post with overposting

Defending against the attack

So how can you prevent this attack? Luckily there's a whole host of different ways, and they are generally the same as the approaches you could use in the previous version of ASP.NET. I'll run through a number of your options here.

1. Use BindAttribute on the action method

Seeing as the vulnerability is due to model binding, our first option is to use the BindAttribute:

public IActionResult Safe1([Bind(nameof(UserModel.Name))] UserModel model)
{
    return View("Index", model);
}

The BindAttribute lets you whitelist only those properties which should be bound from the incoming request. In our case, we have specified just Name, so even if a user provides a value for IsAdmin, it will not be bound. This approach works, but is not particularly elegant, as it requires you specify all the properties that you want to bind.

2. Use [Editable] or [BindNever] on the model

Instead of applying binding directives in the action method, you could use DataAnnotations on the model instead. DataAnnotations are often used to provide additional metadata on a model for both generating appropriate markup and for validation.

For example, our UserModel might actually be already decorated with some data annotations for the Name property:

public class UserModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }

    [Editable(false)]
    public bool IsAdmin { get; set; }
}

Notice that as well as the Name attributes, I have also added an EditableAttribute. This will be respected by the model binder when the post is made, so an attempt to post to IsAdmin will be ignored.

The problem with this one is that although applying the EditableAttribute to the IsAdmin produces the correct output, it may not be semantically correct in general. What if you can edit the IsAdmin property in some cases? Things can just get a little messy sometimes.

As pointed out by Hamid in the comments, the [BindNever] attribute is a better fit here. Using [BindNever] in place of [Editable(false)] will prevent binding without additional implications.

3. Use two different models

Instead of trying to retrofit safety to our models, often the better approach is conceptually a more simple one. That is to say that our binding/input model contains different data to our view/output model. Yes, they both have a Name property, but they are encapsulating different parts of the system so it could be argued they should be two different classes:

public class BindingModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }
}

public class UserModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }

    [Editable(false)]
    public bool IsAdmin { get; set; }
}

Here our BindingModel is the model actually provided to the action method during model binding, while the UserModel is the model used by the View during HTML generation:

public IActionResult Safe3(BindingModel bindingModel)
{
    var model = new UserModel();

    // can be simplified using AutoMapper
    model.Name = bindingModel.Name;

    return View("Index", model);
}

Even if the IsAdmin property is posted, it will not be bound as there is no IsAdmin property on BindingModel. The obvious disadvantage to this simplistic approach is the duplication this brings, especially when it comes to the data annotations used for validation and input generation. Any time you need to, for example, update the max string length, you need to remember to do it in two different places.

This brings us on to a variant of this approach:

4. Use a base class

Where you have common properties like this, an obvious choice would be to make one of the models inherit from the other, like so:

public class BindingModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }
}

public class UserModel : BindingModel
{
    public bool IsAdmin { get; set; }
}

This approach keeps your models safe from mass assignment attacks by using different models for model binding and for View generation. But compared to the previous approach, you keep your validation logic DRY.

public IActionResult Safe4(BindingModel bindingModel)
{
    // do something with the binding model
    // when ready to display HTML, create a new view model
    var model = new UserModel();

    // can be simplified using e.g. AutoMapper
    model.Name = bindingModel.Name;

    return View("Index", model);
}

There is also a variation of this approach which keeps your models completely separate, but allows you to avoid duplicating all your data annotation attributes by using the ModelMetadataTypeAttribute.

5. Use ModelMetadataTypeAttribute

The purpose of this attribute is to allow you defer all the data annotations and additional metadata about you model to a different class. If you want to keep your BindingModel and UserModel hierarchically distinct, but also son't want to duplicate all the [MaxLength(200)] attributes etc, you can use this approach:

[ModelMetadataType(typeof(UserModel))]
public class BindingModel
{
    public string Name { get; set; }
}

public class UserModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }

    public bool IsAdmin { get; set; }
}

Note that only the UserModel contains any metadata attributes, and that there is no class hierarchy between the models. However the MVC model binder will use the metadata of the equivalent properties in the UserModel when binding or validating the BindingModel.

The main thing to be aware of here is that there is an implicit contract between the two models now - if you were to rename Name on the UserModel, the BindingModel would no longer have a matching contract. There wouldn't be an error, but the validation attributes would no longer be applied to BindingModel.

Summary

This was a very quick run down of some of the options available to you to prevent mass assignment. Which approach you take is up to you, though I would definitely suggest using one of the latter 2-model approaches. There are other options too, such as doing explicit binding via TryUpdateModelAsync<> but the options I've shown represent some of the most common approaches. Whatever you do, don't just blindly bind your view models if you have properties that should not be edited by a user, or you could be in for a nasty surprise.

And whatever you do, don't bind directly to your EntityFramework models. Pretty please.

Andrew Lock | .Net Escapades
Want an email when
there's new posts?