Click here to Skip to main content
15,867,835 members
Articles / Web Development / ASP.NET / ASP.NET4.0

Prevent URL Tampering in ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
14 Sep 2016CPOL8 min read 41.4K   300   7   2
This article explains the security concerns in respect to URLs with plain ids and unique identifiers and presents a solution using custom attributes in ASP.NET MVC 5.

Introduction

This article presents an approach to prevent the modification of url in ASP.NET MVC web application. Security is one of the major concerns of web applications and different frameworks provide many built in features to make application more robust and fool proof. But there are still some areas where the developers need to think which technique suits best for the application depending on the nature of application. Here we discuss the problem of open Ids and other unique identifiers in the url (mostly in query string) which can easily be manipulated by the user and thus leads to data leak. Microsoft dev team is aware of this url corruption problem and hence provided many security features in recent releases of ASP.NET especially in MVC i.e. Identity authentication, Authorization attribute and Anti Forgery tokens to name a few. However, refraining users to change query string parameters manually is still up to developers to handle themselves. Here, I demonstrate a solution using MVC filters and attributes which is one of the few solutions to solve this problem. It is based on ASP.NET MVC 5 using Ninject IOC container.

Background

During the transition from ASP.NET Web forms to MVC a lot has been changed and now web applications are more cleanly written with a proper separation of concerns. One of many powerful features of MVC is Url routing, which has really transformed web urls and make them simple, easy and understandable for users. We have moved from ugly long urls to beautifully crafted small ones with the help of powerful routing engines but during the process we’ve also exposed lot of technical information to users; the simpler the urls are in browser address bar, the easier to forge it. For instance, let’s look at this url which returns the details of an order http://websitename.com/Order/1, now it’s very easy to understand even for a non-technical person what this url does and if someone simply changes the id at the end he/she can get order details for some other user. This problem has been addressed and there are many different ways to solve it. Here we briefly discuss few of them and then move on to our proposed solution.

1) Encryption: Very popular way to address this problem is to encrypt the url or just part of it. This is a good solution where the url looks something like this http://websitename.com/Order/Aj129Lo0)3387TRW and it becomes very difficult for a user to guess. But the encryption algorithm should be very good and able to produce long and strong hashes otherwise security can be compromised if someone applies brute force attack. On top of that there is an overhead of encryption and decryption which can impact the performance.

2) GUID: Another solution which is acceptable by many experts is to use GUID for primary Ids in database instead of simple integer Ids. It’s good if you’re writing your application from scratch but difficult to implement for legacy applications with old databases.

3) Authorization: MVC Authorization attribute helps to find whether user is authorized but it does not tell whether user is authorized to access particular resource in our case “Customer Order”.

Proposed Solution:
The solution in this article is implemented using MVC custom attribute which identifies the type of resource from the action method’s parameter list and then call a service method to see whether user is authorized to access particular resource. This real time check against database or any persistent storage container like session or cache, makes it fool proof.

Using the code

The whole idea is to do a real time check against the storage container i.e. database, session, cache, etc. before action method is executed. This solution is based on ASP.NET MVC platform and used Ninject IOC container for dependency injection. Additionally, it uses Nuget package called “Ninject.MVC5” by Remo Gloor for dependency injection in attributes. Here, I’ve used Ninject because it has good support for attribute injection but the solution is not dependent on Ninject and it can be replace by any other IOC container. It can also be achieved without dependency injection but I prefer not to do it.

In the attached demo project I’ve used a simple example of customer and their product orders. For the sake of simplicity the models only contain required properties and used in views. In reality it can be a very complex application with a proper domain models and view models but here the focus is to explain how to prevent url modifications. Application is a default MVC application created by Visual Studio 2015 MVC template where Home controller has been modified to show list of orders for a customer with username testuser@test.com and the details of the order if user clicks the details link.

To use the demo project, run the application and then first register a user with a username/email equals to testuser@test.com and then login with that user to see the order list. There are prepopulated two orders for this user which I just hard coded in CustomerService constructor to avoid any database calls for simplicity.

Two core components of the project are (i) ResourceTypeAttribute and (ii) CustomAuthorizeAttribute. ResourceTypeAttribute is used to identify the requested resource by the user and CustomAuthorizeAttribute does the actual check to see if user has got the authority on that resource before the action is executed. Besides those attributes, there is a CustomerService class which implements ICustomerService interface to retrieve data and do the checks.

ResourceTypeAttribute

It’s a parameter attribute that is placed under Attributes folder in the project.

C#
    [AttributeUsage(AttributeTargets.Parameter)]
    public class ResourceTypeAttribute : System.Attribute
    {
        public readonly ResourceTypeEnum ResourceType;
        public ResourceTypeAttribute(ResourceTypeEnum resourceType)
        {
            ResourceType = resourceType;
        }

    }

A typical application consists of different type of resources. In this customer order example some of the resources are customer, product, order, etc. Generally, the url’s query string contains resource Ids and hence at the time of request there should be a way to identify the type of resource for the requested id or unique identifier. This attribute will be used on action method’s parameters to distinguish the type and trigger the authorization check. An enum for resource type is passed to the attribute to specify the type.

C#
public ActionResult OrderDetails([ResourceType(ResourceTypeEnum.OrderId)] int orderId)
{
     Order orderDetails = _customerService.GetOrderDetails(orderId);
     return View(orderDetails);
}

CustomAuthorizeAttribute

After specifying the parameter attribute to action method the next important thing is how to trigger an attribute before the execution of action method to authorize the request. This can be done by extending existing MVC Authorize attribute but the challenge was to inject customer service in the attribute and to achieve that we use a combination of action filter and attribute called “CustomAuthorizeAttribute” and “CustomAuthorizeFilter”. Here attribute is used just for triggering purpose and actually code implementation is contained in action filter.

C#
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using PreventUrlModifications.Attributes;
using PreventUrlModifications.Services;

namespace PreventUrlModifications.Filters
{

    ///marker attribute
    public class CustomAuthorizeAttribute : FilterAttribute { }

    public class CustomAuthorizeFilter : IActionFilter
    {
        private readonly ICustomerService _customerService;

        public CustomAuthorizeFilter(ICustomerService customerService)
        {
            _customerService = customerService;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {           

        }


        public void OnActionExecuting(ActionExecutingContext filterContext)
        {

            var user = filterContext.HttpContext.User;

            if (user != null && user.Identity.IsAuthenticated)
            {

                foreach (var parameter in filterContext.ActionDescriptor.GetParameters())
                {

                    foreach (var attribute in parameter.GetCustomAttributes(false))
                    {
                        var paramAttribute = attribute as ResourceTypeAttribute;

                        if (paramAttribute != null)
                        {

                            ResourceTypeAttribute resource = paramAttribute;

                            var type = resource.ResourceType;

                            object value = null;


                            if (filterContext.ActionParameters.ContainsKey(parameter.ParameterName))
                            {
                                value = filterContext.ActionParameters.FirstOrDefault(x => x.Key == parameter.ParameterName).Value;
                            }

                            bool authorised = _customerService.IsCustomerAuthorised(type, value, user.Identity.GetUserName());

                            if (!authorised)
                            {
                                // if it's an ajax call
                                if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
                                {
                                    //Sign Off
                                    HttpContext.Current.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
                                    JavaScriptResult result = new JavaScriptResult();
                                    result.Script = "window.location = '/'";
                                    filterContext.Result = result;

                                }
                                else
                                {
                                    HttpContext.Current.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
                                    filterContext.Result = new RedirectResult("~/Account/Login");
                                }
                            }
                        }
                    }

                }
            }
        }

    }

}

This action filter is the essence of the solution. Here in “OnActionExecuting” method it first checks if user is authenticated and then get the parameter list of action method and then search for any parameter with “ResourceTypeAttribute”. If it finds a parameter with that attribute then it gets its value and pass those information to CustomerService method to authorize them. If it gets authorized then nothing happens otherwise it logs the user out and redirects user to login page.

Customer Service

It is just a sample service created to retrieve data for demo purposes. In real application it could be replaced with some other service or services responsible for data retrieval and authorization checks.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using PreventUrlModifications.Models;
using PreventUrlModifications.Enums;

namespace PreventUrlModifications.Services
{
    public class CustomerService : ICustomerService
    {
        private readonly List<Customer> _customers = new List<Customer>();
        private readonly List<Order> _customerOrders = new List<Order>();

        public CustomerService()
        {

            _customers.Add(new Customer() {Username = "testuser@test.com", Name = "ABC"});
            _customers.Add(new Customer() { Username = "secondtestuser@test.com", Name = "XYZ" });
            _customerOrders.Add(new Order() {Id = 1, CustomerUsername = "testuser@test.com", ProductName = "Product1", OrderDate = DateTime.Now.AddYears(1), Cost = 10});
            _customerOrders.Add(new Order() { Id = 2, CustomerUsername = "testuser@test.com", ProductName = "Product2", OrderDate = DateTime.Now.AddYears(2), Cost = 20 });
            _customerOrders.Add(new Order() { Id = 3, CustomerUsername = "secondtestuser@test.com", ProductName = "Product3", OrderDate = DateTime.Now.AddMonths(2), Cost = 5 });

        }


        public IEnumerable<Order> GetAllCustomerOrders(string customerId)
        {
            return _customerOrders.Where(x => x.CustomerUsername == customerId);
        }


        public Order GetOrderDetails(int orderId)
        {
            return _customerOrders.FirstOrDefault(x => x.Id == orderId);
        }
      
        public bool IsCustomerAuthorised(ResourceTypeEnum resourceType, object resourceId, string username)
        {

            bool authorised = false;           

            if (resourceId == null)
                return false;           

            switch (resourceType)
            {
                case ResourceTypeEnum.OrderId:
                    int orderId = 0;
                    Int32.TryParse(resourceId.ToString(), out orderId);
                    authorised = _customerOrders.Any(x => x.Id == orderId && x.CustomerUsername == username);
                    break;
            }

            return authorised;

        }

    }

}

Ninject Binding

Finally, to apply attributes to action methods and inject dependency we use Ninject. We rely on Nuget package called “Ninject.MVC5” by Remo Gloor which provides bind filter extension to inject dependencies in attributes and action filters. When you add this package it will create a file in App_Start folder with the name “NinjectWebCommon” which is responsible for all the bootstrapping. You just need to add your bindings in RegisterServices method. To get more information on Ninject bindings visit this wiki page https://github.com/ninject/Ninject.Web.Mvc/wiki/Dependency-injection-for-filters

C#
using System.Web.Mvc;
using Ninject.Planning.Bindings;
using Ninject.Web.Mvc.FilterBindingSyntax;
using PreventUrlModifications.Filters;
using PreventUrlModifications.Services;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(PreventUrlModifications.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(PreventUrlModifications.App_Start.NinjectWebCommon), "Stop")]

namespace PreventUrlModifications.App_Start
{

    using System;
    using System.Web;
    using Microsoft.Web.Infrastructure.DynamicModuleHelper;
    using Ninject;
    using Ninject.Web.Common;

    public static class NinjectWebCommon
    {

        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>

        public static void Start()
        {

            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));

            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));

            bootstrapper.Initialize(CreateKernel);

        }

        /// <summary>
        /// Stops the application.
        /// </summary>

        public static void Stop()
        {

            bootstrapper.ShutDown();

        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>

        private static IKernel CreateKernel()
        {

            var kernel = new StandardKernel();

            try

            {

                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);

                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();


                RegisterServices(kernel);

                return kernel;

            }

            catch

            {

                kernel.Dispose();

                throw;

            }

        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>

        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<ICustomerService>().To<CustomerService>();
            kernel.BindFilter<CustomAuthorizeFilter>(FilterScope.Controller, 0).WhenControllerHas<CustomAuthorizeAttribute>();

        }       

    }

}

Wiring all up

After creating all those attributes, services and binding you need to perform following steps to wire them all up.

  1. First you need to apply your MVC built in Authorize attribute to the controller which can take care of user authentication.
  2. Second step is to apply “CustomAuthorizeAttribute” to the controller where resource authorization required.
  3. Apply ResourceTypeAttribute to the action method’s parameter.

Run and test application

There are no unit tests in this project but to see attribute in action follow below mentioned steps.

  1. Clear all cookies related to localhost to avoid any conflicts.
  2. Build the application, it will restore all nuget packages.
  3. Run the application and register a user with user name testuser@test.com and then login with that user.
  4. Click on home and it will show you the list of all orders (which are 2 in this case).
  5. Then click the link “click to see order details” for one of the orders. It will take you to order details view and the url will be something like this http://localhost:53572/OrderDetails/1.
  6. Now if you change the “Id” at the end of url to any number greater than 2 e.g. http://localhost:53572/OrderDetails/3 and hit enter, system will throw you out to login page b/c Order Id 3 is not associated to user testuser@test.com.

Points of Interest

There are still few more things to be done to improve the code for example what happens if action accepts view model instead of Id? I’ve not implemented that yet but the idea is to extend that ResourceTypeAttribute and accepts property name of the resource in the model. If property name is known then we can get the value of that property from model.

Another enhancement to reduce the number of database hits if service makes a database for each authorization check is to cache the data after getting it from database for the first time.

The technique discussed in this article can also be achieved using AOP (aspect oriented programming) but then not everyone is fan of AOP. PostSharp (https://www.postsharp.net/aspects) is one of the good frameworks available to implement aspects in .NET.

License

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


Written By
Technical Lead
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiondownload link is broken Pin
Tridip Bhattacharjee14-Sep-16 22:03
professionalTridip Bhattacharjee14-Sep-16 22:03 
AnswerRe: download link is broken Pin
STalha.Munir14-Sep-16 23:18
STalha.Munir14-Sep-16 23:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.