Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASP

Securing Ids on Web Applications

3.00/5 (1 vote)
13 Nov 2018CPOL6 min read 8.6K  
How to secure Ids on web applications

Introduction

Recently, I have been reading through the documentation and articles about how to protect our web applications from tampering when giving a form to the user. Looking at this, one realizes that the cause for these security measures is due to the inherent nature of HTTP, in that being stateless, we have to find ways around it.

Before we start, I wanted to go a little through the standard practices that are present in the default scaffolding for ASP .NET applications, though they are a good practice using other platforms as well. Next, we will discuss the main topic of this article, which when thinking about it, it might be overkill or not; anyway it is a real risk.

The Worn-Out Path

So we can get some hands-on examples, for the remainder of this post, we’ll be working on a new ASP .NET Core application (doesn’t necessarily have to be .NET Core, the .NET Framework would work as well).

In this application, we will be using the following model class:

C#
using System;
public class Student
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime EnrollementDate { get; set; }
    public int Credits { get; set; }
}

So far, it is just an average model class that we’ve always seen, an Id and a set of properties.

Now with this model, we will create a new controller with scaffolding for this model and let’s inspect the actions and views that generated, and the implemented security measures.

C#
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Create([Bind("Id,FirstName,LastName,EnrollementDate,Credits")] Student student)
C#
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Edit(int id, [Bind("Id,FirstName,LastName,EnrollementDate,Credits")] Student student)

Here, we can see the following two best practices; we are going to go through them and add some additional comments:

  • The [ValidateAntiForgeryToken] attribute – this helps us from cross-site scripting attacks, what this means is that when the page is created, a unique token is generated for that page, so that when it is posted, we will know if it came from our page or not. That way, we prevent others from posting to our websites from other sites like phishing attacks.
  • The [Bind(“…”)] attribute – this is an excellent addition to the workflow, and it helps us with overposting. To understand overposting, first, we must realize that the MVC framework uses a construct called model binders, which take post information and transforms it into an actual Student object with all its fields filled in. Because of this, if we’re not careful, someone can intercept or modify the page in their browser such as, they can overwrite fields that weren’t even exposed in the form. As an example, if the property Credits was not on the form page, but someone sent that field filled out anyway, then the Student object that will be created through model binding will have that property filled in as well. So using the [Bind] attribute, we can explicitly tell which fields/properties we want to find and ignore the rest.

Though personally, I see a better alternative to the [Bind] attribute which also plays well into the changes we want to make, and that option is by using view-models or data-transfer-objects (though this approach requires additional infrastructure in assigning the fields manually or using a framework like AutoMapper), for example if we wanted to hide the Credits property, we would have the following:

C#
public class StudentViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime EnrollementDate { get; set; }
}

As we can see in the view-model, if there is no Credits field, then it cannot be filled in by the model binder.

What About Ids? Should We Care?

Well, as we saw, overposting can cause a real issue, the downside, especially for edit/update forms is that there is no security check for Ids.

C#
var student = await _context.Student.SingleOrDefaultAsync(m => m.Id == id);

Lines the one above are quite frequent, all we care about when it comes to Ids is that there is one in the system. But what if someone wants to be mischievous?

WARNING: The following scenarios are fictional, nevertheless, they should not be acted upon, they are only presented as examples to raise awareness, please do not change information in a system for which you are not authorized to make changes to.

Here is a scenario that is plausible:

In our system, only the staff can update a student’s credits, so they need to be authorized, but even when permitted, a particular teacher might be restricted to students they have classes with. So let’s say the teacher has access to the system but not a specific student, then, unless there are other safeguards in place other than the ability to see a page (aka only client-side validation), then that person could change information in a form for their student but switch the Id to someone that is not in their class.

Another parallel example is if, in a centralized e-commerce platform, someone can only change the information on products in their location, but by using this approach they could change the data from other places as well.

Again, this happens because we have to save our identifiers somewhere on the page or the URL due to HTTPs stateless nature.

So what can we do to help in this situation? Well, off hand, two fixes come to mind.

  1. Start using GUIDs (globally unique identifiers) instead of integers for your Ids. I personally always use GUIDs for these since they have several benefits when compared with an integer.
    1. GUIDs can be generated offline without first saving them to the database with a slim chance of a conflict.
    2. When doing work in a database, integer Ids might skip a few numbers if there is an error, this will cause holes in your numbering system, and if you have an invoice system, then this would be a real pain to fix, especially if it’s caught weeks after.

    3. GUIDs are unpredictable when generated (as much as they can be), that means that in our previous teacher-student scenario, unless the person that is making the modifications knows the Id explicitly, the chances that they might guess the Id is shallow.

  2. Add a control variable to check that the post for that form is indeed intended for that object. You could either add a hash of the Id included in the page as a hidden input and when the post is done check that they still match up, that way, even if someone did know the Id beforehand, it would be even more challenging to do any damage.

Now let’s see an example of such a hash, a very simple one but still viable:

C#
public class StudentViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime EnrollementDate { get; set; }

    public string SecretHash => (Id ^ 5 + 7).ToString();
}

Here, we added a getter that will return a string based on the Id value; this operation can be as complex as you wish. Next, we will update the edit view:

HTML
<div class="row">
    <div class="col-md-4">

            <div class="text-danger"></div>            
            
            <div class="form-group">

                <span class="text-danger"></span>
            </div>
            <div class="form-group">

                <span class="text-danger"></span>
            </div>
            <div class="form-group">

                <span class="text-danger"></span>
            </div>
            <div class="form-group">

                <span class="text-danger"></span>
            </div>
            <div class="form-group">
            </div>        
    </div>
</div>

Now whenever this form gets posted, the SecretHash will go along with it. And finally, we will update the Edit action method:

C#
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Edit(int id, StudentViewModel studentViewModel)
{
    var student = await _context.Student.SingleOrDefaultAsync(m => m.Id == id);

    var checkViewModel = new StudentViewModel { Id = student.Id };
    if (studentViewModel.SecretHash != checkViewModel.SecretHash)
    {
        // Do something about it
    }
}

There are undoubtedly cleaner ways to deal with this, but the point is that the Id is just as necessary as all the other data, and keep in mind that since we are exposing both the Id and the SecretHash (use other not-so-obvious names).

Conclusion

Do we need such complex systems for Ids? The answer is “I do not know, can you afford to risk it”? Food for thought… 🙂

I hope you enjoyed this example, even to me it sounds a little paranoid at times, but either way, if it can be done, it will be done at some point.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)