Click here to Skip to main content
15,884,472 members
Articles / Web Development / ASP.NET / ASP.NET Core

Preventing Insecure Object References in ASP.NET Core 2.0

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
9 Sep 2017CPOL2 min read 5.9K  
How to prevent insecure direct object reference in ASP.NET Core. Continue reading...

Problem

How to prevent insecure direct object reference in ASP.NET Core.

Solution

Create an empty project and update the Startup class:

C#
public void ConfigureServices(
            IServiceCollection services)
        {
            services.AddMvc();
            services.AddDataProtection();
        }

        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env)
        {
            app.UseMvcWithDefaultRoute();
        }

Create a model:

C#
public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int ReleaseYear { get; set; }
        public string Summary { get; set; }
    }

Create a controller and inject IDataProtectionProvider as dependency:

C#
[Route("movies")]
    public class MoviesController : Controller
    {
        private readonly IDataProtector protector;

        public MoviesController(IDataProtectionProvider provider)
        {
            this.protector = provider.CreateProtector("protect_my_query_string");
        }
     ...

Add a method that retrieves the data and then encrypts the object reference (Id property here):

C#
[HttpGet]
        public IActionResult Get()
        {
            var model = GetMovies(); // simulate call to repository
            
            var outputModel = model.Select(item => new
            {
                Id = this.protector.Protect(item.Id.ToString()),
                item.Title,
                item.ReleaseYear,
                item.Summary
            });

            return Ok(outputModel);
        }

Add a method that receives encrypted object reference and then decrypts:

C#
[HttpGet("{id}")]
        public IActionResult Get(string id)
        {
            var orignalId = int.Parse(this.protector.Unprotect(id));

            var model = GetMovies(); // simulate call to repository
            
            var outputModel = model.Where(item => item.Id == orignalId);

            return Ok(outputModel);
        }

Running the sample (browsing to /movies) with show encrypted references:

Image 1

Discussion

OWASP 2013 classifies Insecure Direct Object Reference as one of the Top 10 risks and is present if object references (e.g. primary key of a database record) can be manipulated for malicious attacks.

One possible method to prevent is shown in the example above, i.e., by encrypting the internal references we can hide the internal details of our database/application structure. We can encrypt in various ways, here I am using the new Data Protection API in ASP.NET Core.

Data Protection API

There are two key abstractions we utilize to encrypt data, IDataProtectionProvider and IDataProtector. We use the provider to create a protector by calling its CreateProtector() method. This method takes in a string key (known as Purpose String). Once we have a protector, we can use its Protect() method to encrypt and Unprotect() method to decrypt the data.

Purpose Strings

It’s a key that ensures isolation between different protectors (cryptographic consumers), i.e., data encrypted by protector A cannot be read by a protector B, as long as they use different purpose strings.

Exception

Trying to decrypt the data that has been modified will throw CryptographicException:

Image 2

Limited Lifetime

You could also encrypt data that can be decrypted for only a limited period of time (e.g. creating tokens for password reset link). In order to achieve this, we use ITimeLimitedDataProtector and specify a time period when protecting the data:

C#
private readonly ITimeLimitedDataProtector protector;

        public MoviesController(IDataProtectionProvider provider)
        {
            this.protector = provider.CreateProtector("protect_my_query_string")
                                     .ToTimeLimitedDataProtector();
        }

        [HttpGet]
        public IActionResult Get()
        {
            var model = GetMovies(); // simulate call to repository
            
            var outputModel = model.Select(item => new
            {
                Id = this.protector.Protect(item.Id.ToString(), 
                                            TimeSpan.FromSeconds(10)),
                item.Title,
                item.ReleaseYear,
                item.Summary
            });

            return Ok(outputModel);
        }

Trying to decrypt the data after its expiry will throw CryptographicException:

Image 3

Tip: Action Filter

You could create an action filter that decrypts the incoming encrypted reference, this can be reused across your application:

C#
[HttpGet("{id}")]
        [DecryptReference]
        public IActionResult Get(int id)
        {
            var model = GetMovies(); // simulate call to repository

            var outputModel = model.Where(item => item.Id == id);

            return Ok(outputModel);
        }

A simple filter (note I am using typed filter as it has dependency injection):

C#
public class DecryptReferenceFilter : IActionFilter
    {
        private readonly IDataProtector protector;

        public DecryptReferenceFilter(IDataProtectionProvider provider)
        {
            this.protector = provider.CreateProtector("protect_my_query_string");
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            object param = context.RouteData.Values["id"].ToString();
            var id = int.Parse(this.protector.Unprotect(param.ToString()));
            context.ActionArguments["id"] = id;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {

        }
    }

    public class DecryptReferenceAttribute : TypeFilterAttribute
    {
        public DecryptReferenceAttribute() :
            base(typeof(DecryptReferenceFilter))
        { }
    }

Configuration

You could configure the data protection API when configuring its service:

C#
public void ConfigureServices(
            IServiceCollection services)
        {
            services.AddMvc();
            services.AddDataProtection()
                    .SetApplicationName("Fiver.Security")
                    .PersistKeysToFileSystem(new 
                                  DirectoryInfo(@"C:\MyKeys"))
                    .SetDefaultKeyLifetime(TimeSpan.FromDays(7)) // 7 days is minimum
                    .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
                    {
                         EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, // default
                         ValidationAlgorithm = ValidationAlgorithm.HMACSHA256   // default
                    });
        }

Here I am configuring:

  • Application name, e.g., in order to have multiple projects in my application (e.g. APIs) decrypt the data
  • Location where keys are stored
  • Lifetime of keys. Default is 90 days and minimum being 7 days
  • Cryptographic algorithm: just resetting to default explicitly for demonstration purposes

License

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



Comments and Discussions

 
-- There are no messages in this forum --