ASP.NET MVC Security through Validation

Comments 2

Share to social media

In IT development work, security is everyone’s responsibility. Most computer security breaches do not usually occur as a result of a single vulnerability. They happen because a breach exposes other security weaknesses to the intruder. Systems that rely on a single line of defense easily allow bad things to be done on the computer system. SQL Injection, for example, is impossible unless the user-interface fails to perform some very simple validations on the input. Every component in an application should assume that it is possible for a malicious intruder to gain access it, and take sufficient measures to prevent unauthorized penetration.

As software developers creating web applications, we have prime responsibility to help stop or slow down computer security breaches. This is because web applications are often the most visible, accessible, and most vulnerable component in a computer system and they make a very enticing initial target.

Defense in Depth

Let’s start with defense in depth. This concept simply means that instead of relying on a single line of defense, access is controlled to all levels of the application. Software security is analogous to network security where we may have many systems and devices protecting the network such as …

  • Firewalls
  • Intrusion detection systems
  • DMZ
  • logging and auditing
  • access control cards
  • multi factor authentication
  • sandboxing
  • antivirus software

In a web application, we should additionally have the following:

  • client-side validations
  • server-side validations
  • data access validations
  • business logic checks
  • database constraints

We need to validate user input, and catch malicious usage as early as possible. We want to validate it multiple times as part of defense in depth, but we’d like these checks to be independent. We would wish to avoid repeating the same logic to implement validation checks (this is the DRY principle), and we also want to keep the validation logic separate from the other business logic.

This is a tall order, but is exactly the role that Declarative Validations is designed to fill.

Client-side validations are generally a convenience designed to assist users who are playing by the rules, and are intended to catch mistakes without requiring a round trip to the server for validation, but even here we can detect users who are not playing by the rules, such as when they attempt ‘Little Bobby Tables’ SQL Injection via normal input. This won’t catch all possible input though, because there are motivated users who may skip our pretty UI and send packets and requests directly to the server. This illustrates why we need server-side validations as well.

This validation logic is potentially a lot of code, but it is important that it is done. The consolation is that Declarative Validations will help to ease the work.

Declarative Validations

Declarative Validations allow us to add attributes to the properties in our Models to express validation rules. This provides a single location, and a simple way, to specify these rules. The framework will handle the task of interpreting these rules and adding the necessary JavaScript to support client-side validation. The ModelBinder will look after the job of interpreting these rules as part of model-binding to ensure that server-side validation is covered. All we have to do is make sure that the attributes are properly added to the Model.

A common simple AddressModel might include properties like this:

Properly decorated with some appropriate validation attributes, the AddressModel might look like this:

This is the basic set of validations that you should include. If you are prompting for a string, always specify how long that string should be. If a property does not make sense to be left blank, mark it as required. You can potentially get very creative with Regular Expression validators, but try to keep your expressions simple because they can easily get out of hand.

If you are using some form of Code-Generation to create your Models, you may also want to use the MetadataType attribute to associate another class to the generated type. This allows you to generate your model while keeping your attributes in a separate class that will be protected when you regenerate your model. This is helpful because the Metadata that is used to drive your code-generation may not be sufficient to identify all of the validation attributes that you would want: You may need to have more control over the validation message than your code generation strategy allows.

It is a good practice to provide an alternative way to add additional validation attributes beyond what is possible by using only your code-generation approach.

Using this new attribute, our Model looks much more like our original model …

… and the Metadata Type looks similar to our fully decorated Model; but there are a couple of exceptions that make it a little easier to work with:

You may notice that not all of the properties are listed. Any property that is listed in the metadata type must be included in the type with which it is associated. We cannot, for example, have an AddressLine3 in the metadata type unless there is also one in the associated type: However, any property from the associated type that does not need an attribute can be excluded from the metadata type.

You may also notice that all of the properties are of type object. We can get away with this because the matching of properties happens on the property name only. The data type for the property is not considered when matching.

Remote Validation

The Remote attribute allows you to specify an Action in a Controller to be called to handle the task of validating a property. This makes it very easy to develop custom validation without having to get your hands dirty with JavaScript. The client-side validation is handled asynchronously through a call-back to logic on the server-side.

We might, for example, do such a validation on the City to ensure that the City selected is in the State entered. Our City property would be modified to look like this:

The AdditionalFields parameter tells the framework to also send over the State property when doing the validation.

As long as the client has JavaScript enabled, this Action will be called. It is up to you to determine what needs to happen to handle the validations. You have at your disposal everything that you can do from the Server. In the end, this Action should return a JsonResult with a boolean embedded for whether it is valid or not.

The Action that gets called would look like this:

This sounds perfect, but there is a problem.

Ironically Remote validations will not be automatically handled on the server-side. It is somewhat ironic that an attribute designed to allow validation logic to be remotely run on the server from the client will not be run on the server.

If you open this method with Reflector, you can see why.

2127-img24.jpg

Instead of running the logic specified by the Controller and Action, the attribute has the IsValid hard-coded to always return true.

So if you are going to use remote validations, which you still should, just remember to explicitly validate the properties before trusting that they are valid.

Under Posting / Over Posting and Validation

If the client sends over an unexpected amount of data, you can get problems. ‘Under Posting’ occurs when the client sends less data than expected. ‘Over Posting’ happens when the client sends more data than expected. Both can cause problems if you don’t make provision for this happening.

To see how this might work, let’s extend our model to include a new property for DateLastVerified:

If the client did not send over a value for DateLastVerified, we have Under Posting. One step in solving this problem is to add a Required attribute to the property, but this is not sufficient. If client-side validations are disabled or by passed the client could send a request to the server that does not include this value. The frustrating thing is that, as configured, server-side validations will not catch this either.

Why?

The problem lies in the guts of the model binder. As our model is created and bound, the DateLastVerified property will get a default value. In this case 1/1/0001 12:00:00 AM. This means that even though the client did not send over a value, the property passes the Required validation.

So marking the property as required is part of the solution but not the whole story. The next step seems counter-intuitive. In addition to marking the property as required, meaning that the value should never be null, we need to change the data type to a nullable DateTime.

With the data type changed to DateTime?, the default value will be null so we do not get the deceptive value of DateTime.MinValue and the Required validation rule can pick up on the fact that a valid value was not provided for the DateLastVerified.

So the final version of the model would look something like this:

OverPosting happens when the client sends over data that we don’t expect to be updated.

Suppose that you reused this Model for a View that did not allow the user to edit the IsVerified property. It could be that you want to allow the user to edit the address and, if an edit is made, then clear the IsVerified property. Even though the View does not prompt for this value, a valid value could be sent to the server. In this case, the Model Binder will still attempt to set values for all properties including the IsVerified property.

As we saw with UnderPosting, if there is no value sent back to the server it will be initialized to a default value. For a Boolean this will be false. In this case, this is what we want. If you make an edit, clear the IsVerified property. Unfortunately, if there is a value passed back to the server, such as true, this value will be bound by the Model Binder even though the View did not prompt for it.

A malicious user could override your logic if they send a value back to the server that is not prompted-for but nonetheless accepted by your Action. If your Action accepts a property, the Model Binder will try to bind it even if the View did not prompt for it or if the Action is not using it.

To protect against this, you have two options:

  • Use a Model specific to your View that only includes properties needed for the View
  • Ignore Model properties in your Action that are not prompted for by the View

OverPosting and UnderPosting create subtle problems that are easily missed but can potentially short-circuit your application level. Fortunately they can easily be avoided with a little care.

Conclusion

Computer security is complex and there is always controversy over the ideal strategy, and particularly where in the application security checks should be made. Security is, unfortunately, one of those annoying areas in life where you can do everything right and still fail.

Still it falls on all of us to do our part to build secure computer systems, and defense in depth makes a lot of sense. Validating user input at the point of input is an obvious key first step, and ASP.NET MVC makes the validation task a lot less tiresome to develop. In this article we have seen how MVC makes it easier than ever to properly validate input.