Securing Your ASP.Net App Data That Has an Insecure Direct Object Reference

Introduction

As an Aerospace Engineer at X94, your boss has asked you to retrieve the technical drawings on a specific patent from Building 2.  Unfortunately, access to the building requires you to produce proof you have access to the building and you promptly do that in the form of a badge to security.  Once on the 13th floor, access to the Architects and Engineer drawings library requires verification by their biometrics system that you are who you claim to be.  Finally at your destination, you provide the librarian an alpha-numeric code that means nothing to you, but in the right hands, can be converted to the actual index of where to find the technical drawings you have come for.

In the preceding analogy we can easily identify the security measures in place to safe guard access to sensitive data.  In addition to the verifications that an individual has the required access, one additional security measure that might not have been so obvious was the obfuscation of the technical document's identity in the form of a alpha-numeric code that indirectly maps to the actual document's identification and location within the library.

From a figurative point of view, this analogy is the answer to a prevalent web application security flaw referred to as “Insecure Direct Object Reference” and is listed as #4 on OWASP's top 10 most critical security flaws.  But if this is the answer, your next question naturally would be “what is the problem and how does it relate to my web application?”.
 
Insecure Direct Object Reference

We're all familiar with the idea of showing products on our website, such as users making requests to see product details, adding products to their shopping cart or some other similar activity.  Most likely you leverage the product's ID to identify the product the user was requesting to see the details of, add it to their cart and so on.  On top of that, this ID very likely was the primary key of the database table where the product information is stored.  In this case, what we have is a direct object reference. The product (object) on the web page was identified by an ID that was a direct reference to the same identifier in the database.

“Yeah, so what?” Well, for the simple business-to-customer scenarios, the preceding would not be a problem.  But what if this was a financials application, such as your primary bank's website that might retain information such as different checking accounts, savings accounts and other sensitive data?  Imagine your accounts page, you have selected to see the details of your savings account with ID 1344573490:

starcapital
 
As an authenticated user Mary Wiggins, the website displays information specific to your savings account.

account info
 
This checking account is something that we can directly identify as our account and confirm this is a direct reference.  But what if you decide to change the accountNumber parameter from 1344573490 to 1344573491?

account info
 
Erin Maley, who's Erin Maley?  That's not us; we are clearly still authenticated as Mary Wiggins.  All we have done is sequentially increase the account number to the next possible value and we're seeing information for an account that we are not the account holder of.  In this example we have a direct reference of an account that can be identified anywhere within the system by the account number.  Furthermore, we are exercising a potential problem of directly referencing an account that exposes it to simple engineering of the data.

If you're thinking to yourself that this is not the fault of a direct reference problem, but a problem of authorization, you would be partially correct.  What we are seeing is actually two problems when we talk about an Insecure Direct Object Reference flaw.  I find the following to clarify what this flaw is really describing:

authorization and obsfucaton
 
If an insecure direct object reference is a case of both (1) leaking sensitive data and (2) lack of proper access controls then what are our options for mitigating this security flaw and when should it be applied?  We'll start with the mitigation with the biggest impact and widest influence, proper access controls.
 
Multiple Level Access Controls

As in the example at the opening of this article, multiple levels of access might be required.  We might have access to the building, but individual access might be required for special areas within it.  When we think about securing resources within our web application, we can approach it with the same principle in mind.
 
Route Access Control

The first question is, is the current user even authorized to make a request for the resource?  How can we determine whether the current user should be allowed to make the request if we don't know anything about the user?  Therefore the first step we can take in securing a resource is applying an access control to the user interaction in question.

In ASP.NET a user interaction would be our controller action. We can apply the [Authorize] attribute on the ASP.NET MVC controller to ensure that any attempts to access an action on the controller by a user of the system is first authenticated and is not an anonymous user.

  1. [Authorize]  
  2.  public class AccountsController : Controller  
  3.  {  
  4.     [HttpGet]  
  5.     public ActionResult Details(long accountNumber)  
  6.     {  
  7.                //...  
  8.     }  
  9.  }    
This ensures that the API is not open for public consumption and based on your ASP.NET configuration could have the user redirected to the login page (default behavior).  The [Authorize] attribute supports additional constraints by specifying specific Users or Roles: 
  1. [Authorize(Roles = "Admin, Manager")]  
  2.  public class AccountsController : Controller  
  3.  {  
  4.          //..   
  5.   
  6.  }  
The [Authorize] attribute can be applied to controller actions as well for more granular control. An example would be to place an authentication constraint on the controller, but a role-based access control at various actions within the controller.

But an authenticated user is not enough as we have seen in our bank account example, by the fact that we (an authenticated user) can access another user's checking account information.  Abuses like the one seen in the bank account example are commonly referred to as horizontal privilege escalation where users can access information of other users on the same level.  However, authorization to make a request for the resource is quite different than having authorization to the actual resource.
 
Data Access Control

Therefore, the second level and most important access control that we must have in place is to ensure that the user is authorized to access the resource.  In the case of role-based access control, this might be as easy as ensuring the user is part of the proper role. If the resource being requested only requires a certain escalated privilege, you might be able to get away with leveraging the Role property of the [Authorize] as demonstrated earlier.
  1. [Authorize(Roles = "Admin")]  
  2.  public class AccountsController : Controller  
  3.  {  
  4.          //..   
  5.   
  6.  }  
However, more often you will be required to validate authorization at the data level by ensuring the user has access to the requested resource. Depending on many factors, this can be handled a number of ways, but taking our previous bank account details scenario, we can verify the authenticated user is the owner of the account being requested as in the following:
  1. [Authorize]  
  2. public class AccountsController : Controller  
  3. {  
  4.    [HttpGet]  
  5.    public ActionResult Details(long accountNumber)  
  6.    {  
  7.       Account account = _accountRepository.Find(accountNumber);  
  8.       if (account.UserId != User.Identity.GetUserId())  
  9.          {  
  10.             return new HttpUnauthorizedResult("User is not Authorized.");  
  11.          }
  12.    }   
  13. }  
Recall we have the [Authorize] attribute applied at the controller level and don't need to be redundant at the action level.

It is important to note that in the preceding example of issuing an Unauthorized Result with the use of Forms Authentication, ASP.NET will force a 302 redirect to the login page, despite the user being authenticated.  Therefore, depending on your application, your needs and your audience's expectations, it might require making necessary changes to how you handle this behavior. Your options as well as whether you will even need to handle this behavior will vary depending on the framework flavor and use of OWIN modules, as well as your application's needs.

The take away is that the number one mitigation to ensuring there is not an escalation of privileges by a user is to ensure that the proper access controls are in place.  At a minimum we can impose an access control on the request itself as well as access control on the resource being requested.  However, as I have said on a number of occasions, shoring up data leakage in our applications is a security step that should always be evaluated.  What do I mean by “data leakage”?  We can answer that by looking at the other implications of insecure direct object reference, obfuscation.
 
Obfuscation

Obfuscation is the deliberate act of hiding the intended meaning. In our case, we can use obfuscation as a way for inferring security through obscurity.  A simple example involves the identification of people using shortened URLs.  Though security is not the intent, a URL such as http://bit.ly/1Gg2Pnn is obfuscated from what the actual URL represents. The following this a shortened link, Bit.ly can map the obfuscated URL http://bit.ly/1Gg2Pnn to its intended meaning http://lockmedown.com/preventing-xss-in-asp-net-made-easy.

I used the interaction with bank accounts in the financials example because it is a perfect example where there is an element of sensitivity to the metadata.  In this case, a checking account is data that we want to protect. Whereas the account number is metadata about the checking account that we can identify as sensitive.

We saw earlier where we just increased the account number value and were we are able to access the checking account of another user strictly because there was no data level access controls in place.  However, we could erect another defense barrier by obfuscating the account number removing the ability for a malicious user to have the chance to directly engineer the system by changing the values.

Obfuscation can be done on a variety of levels, each providing various levels of security and tradeoffs.  The first option we'll look at is one of the more common, secure but limited options, what I like to call “scoped” indirect reference map.
 
Scoped Indirect Reference Map

A reference map is no different than the example of the Bit.ly shortened URL.  Given a public facing value, your server knows how to map that publicly value to an internal value that represents sensitive data.  Scope represents constraints we put on the map that limits its use.  But enough theoretical discussion, let's look at an example.

We have identified that an account number such as 1344573490 is sensitive data that we want to obscure and provide only enough for the account holder to be able to identify.  To avoid exposing the account number, we can provide a public facing value that is an indirect reference to the account number.  The server will know how to take the indirect reference and map back to the direct reference that is our account number.  The map the server uses is stored in an ASP.NET user session that becomes the scope.  More on the scope in a minute, the following is an implementation:
  1. public static class ScopedReferenceMap  
  2. {     
  3.     private const int Buffer = 32;  
  4.           
  5.     /// <summary>  
  6.     /// Extension method to retrieve a public facing indirect value  
  7.     /// </summary>  
  8.     /// <typeparam name="T"></typeparam>  
  9.     /// <param name="value"></param>  
  10.     /// <returns></returns>  
  11.     public static string GetIndirectReference<T>(this T value)  
  12.     {  
  13.         //Get a converter to convert value to string  
  14.         var converter = TypeDescriptor.GetConverter(typeof (T));  
  15.         if (!converter.CanConvertTo(typeof (string)))  
  16.         {  
  17.             throw new ApplicationException("Can't convert value to string");     
  18.         }  
  19.   
  20.         var directReference = converter.ConvertToString(value);  
  21.         return CreateOrAddMapping(directReference);  
  22.     }  
  23.   
  24.     /// <summary>  
  25.     /// Extension method to retrieve the direct value from the user session  
  26.     /// if it doesn't exists, the session has ended or this is possibly an attack  
  27.     /// </summary>  
  28.     /// <param name="indirectReference"></param>  
  29.     /// <returns></returns>  
  30.     public static string GetDirectReference(this string indirectReference)  
  31.     {  
  32.         var map = HttpContext.Current.Session["RefMap"];  
  33.         if (map == null ) throw new ApplicationException("Can't retrieve direct reference map");  
  34.   
  35.         return ((Dictionary<stringstring>) map)[indirectReference];  
  36.     }  
  37.   
  38.     private static string CreateOrAddMapping(string directReference)  
  39.     {  
  40.         var indirectReference = GetUrlSaveValue();  
  41.         var map =   
  42.            (Dictionary<stringstring>) HttpContext.Current.Session["RefMap"] ??   
  43.                     new Dictionary<stringstring>();  
  44.   
  45.         //If we have it, return it.  
  46.         if (map.ContainsKey(directReference)) return map[directReference];  
  47.   
  48.   
  49.         map.Add(directReference, indirectReference);  
  50.         map.Add(indirectReference, directReference);  
  51.   
  52.         HttpContext.Current.Session["RefMap"] = map;  
  53.         return indirectReference;  
  54.     }  
  55.   
  56.     private static string GetUrlSaveValue()  
  57.     {  
  58.         var csprng = new RNGCryptoServiceProvider();  
  59.         var buffer = new Byte[Buffer];  
  60.   
  61.         //generate the random indirect value  
  62.         csprng.GetBytes(buffer);  
  63.   
  64.         //base64 encode the random indirect value to a URL safe transmittable value  
  65.         return HttpServerUtility.UrlTokenEncode(buffer);  
  66.     }  
  67. }   
  68.    
Here, we have created a simple utility class ScopedReferenceMap that provides extension methods to a value like our bank account 1344573490 into Xvqw2JEm84w1qqLN1vE5XZUdc7BFqarB0 that is our indirect reference.

Ultimately, when an indirect reference value is requested we use a user's session as a way to preserve the mapping between the indirect and direct reference between requests.  The user session becomes the scope of this indirect reference map and enforces a per-user as well as a time constraint on our mapping.  The ability to retrieve the direct reference is only able to be done on a valid and specific user session.

You can utilize it whereever you need to create the indirect reference like your model or view as in the following:

AccountNumber = accountNumber.GetIndirectReference(); //create an indirect reference
 
Now, on an incoming request for the details to an account using the following URL:

local host details
 
We can see both the mapping of the indirect accountNumber reference value back to the direct value in conjunction with our access controls in place:
  1. [HttpGet]  
  2. public ActionResult Details(string accountNumber)  
  3. {  
  4.    //get direct reference  
  5.    var directRefstr = accountNumber.GetDirectReference();  
  6.    var accountNum = Convert.ToInt64(directRefstr);  
  7.   
  8.    Account account = _accountRepository.Find(accountNum);  
  9.   
  10.    //Verify authorization  
  11.    if (account.UserId != User.Identity.GetUserId())  
  12.    {  
  13.        return new HttpUnauthorizedResult("User is not Authorized.");  
  14.    }  
  15.   
  16. }     
In our attempt to acquire the direct reference, if the ASP.NET user session does not obtain a map then it is possible that an attack is being done.  However, if the map exists but it finds the direct reference then it is possible the value has been tampered with.

As I said, the user session creates a scope, a user and time constraint on the ability to map back to the direct reference. These constraints provide additional security measures in themselves.  However, perhaps you have issues with the use of ASP.NET session state, possibly due to its known security weak points, or you have questions about how these constraints play nice with providing RESTful features such as HATEOAS.  Good question.  Let's examine some alternative options.
 
HATEOAS Gonna Hate


If you think about typical interactions with web services, the idea that you make a request and receive a response that contains additional hypermedia links (for example URLs) to additional resources within your web application is an understandable concept for web developers.

This same concept has been heavily refined in one of the pillars of building RESTful web services, Hypermedia as the Engine of Application State or HATEOAS.  A one-sentence explanation of HATEOAS is the ability for web services to provide discoverable actions on a resource that it does by providing hypermedia links in the HTTP response.  This isn't an article on defining RESTful web services, so if REST and HATEOAS are foreign concepts then you're going to need to look to other resources for an understanding.

Therefore, the idea of providing an URL that contains scoped indirect reference parameters is a problem with concepts like HATEOAS or anytime we need to provide durable URLs (URLs that need to have a longer time-to-live).  We must take a different security approach if we want to merge the ability to provide durable URLs that also contains indirect reference values.  So, how can we do that?
 
Static Indirect Reference Map

To provide durable URLs that contain indirect references we will need some way to map from the indirect value back to the original direct value at any given time, or at least for a considerable time in the future.  Constraints such as using a user session to maintain a reference map will not be an option if we want durability. Let's set the stage with a scenario that we would want to use a static indirect reference map.

Imagine you provide a business-to-business web application that allows businesses to acquire pricing for VIP products specific to them. Making a request to the business customer profile view, returns a response that also contains an additional hypermedia link to the VIP product list for this business client.  When following the VIP product link, the response received contains hypermedia links for all the VIP products available for their specific business.

In our example, we decide that we want to obfuscate the VIP product ID's in the VIP product URLs by creating an indirect reference, but with the caveat that we can map back to the direct product ID without a time constraint.

For example https://AppCore.com/business/Acme/VIP/Products/99933

In our situation, encryption would be a good candidate to allow us finer control over the lifetime of mapping an indirect reference back to the direct product ID.

Utilizing the same API as we did with the scoped reference example, let's look at what that would look like and then we'll talk about what we did and why we took this approach, along with concerns and additional alternatives:
  1. public static class StaticReferenceMap  
  2. {  
  3.     public const int KeySize = 128; //bits  
  4.     public const int IvSize = 16; //bytes  
  5.     public const int OutputByteSize = KeySize / 8;  
  6.     private static readonly byte[] Key;  
  7.   
  8.     static StaticReferenceMap()  
  9.     {  
  10.         Key = //pull 128 bit key in  
  11.     }  
  12.   
  13.     /// <summary>  
  14.     /// Generates an encrypted value using symmetric encryption.  
  15.     /// This is utilizing speed over strength due to the limit of security through obscurity  
  16.     /// </summary>  
  17.     /// <typeparam name="T">Primitive types only</typeparam>  
  18.     /// <param name="value">direct value to be encrypted</param>  
  19.     /// <returns>Encrypted value</returns>  
  20.     public static string GetIndirectReferenceMap<T>(this T value)  
  21.     {  
  22.         //Get a converter to convert value to string  
  23.         var converter = TypeDescriptor.GetConverter(typeof (T));  
  24.         if (!converter.CanConvertTo(typeof (string)))  
  25.         {  
  26.            throw new ApplicationException("Can't convert value to string");  
  27.         }  
  28.   
  29.         //Convert value direct value to string  
  30.         var directReferenceStr = converter.ConvertToString(value);  
  31.    
  32.         //encode using UT8  
  33.         var directReferenceByteArray = Encoding.UTF8.GetBytes(directReferenceStr);  
  34.   
  35.         //Encrypt and return URL safe Token string which is the indirect reference value  
  36.         var urlSafeToken = EncryptDirectReferenceValue<T>(directReferenceByteArray);  
  37.         return urlSafeToken;  
  38.     }  
  39.           
  40.     /// <summary>  
  41.     /// Give a encrypted indirect value, will decrypt the value and   
  42.     /// return the direct reference value  
  43.     /// </summary>  
  44.     /// <param name="indirectReference">encrypted string</param>  
  45.     /// <returns>direct value</returns>  
  46.     public static string GetDirectReferenceMap(this string indirectReference)  
  47.     {  
  48.        var indirectReferenceByteArray =   
  49.             HttpServerUtility.UrlTokenDecode(indirectReference);  
  50.        return DecryptIndirectReferenceValue(indirectReferenceByteArray);  
  51.     }  
  52.   
  53.     private static string EncryptDirectReferenceValue<T>(byte[] directReferenceByteArray)  
  54.     {  
  55.         //IV needs to be a 16 byte cryptographic stength random value  
  56.         var iv = GetRandomValue();  
  57.   
  58.         //We will store both the encrypted value and the IV used - IV is not a secret  
  59.         var indirectReferenceByteArray = new byte[OutputByteSize + IvSize];  
  60.         using (SymmetricAlgorithm algorithm = GetAlgorithm())  
  61.         {  
  62.            var encryptedByteArray =   
  63.                GetEncrptedByteArray(algorithm, iv, directReferenceByteArray);  
  64.   
  65.            Buffer.BlockCopy(  
  66.                encryptedByteArray, 0, indirectReferenceByteArray, 0, OutputByteSize);  
  67.            Buffer.BlockCopy(iv, 0, indirectReferenceByteArray, OutputByteSize, IvSize);  
  68.         }  
  69.         return HttpServerUtility.UrlTokenEncode(indirectReferenceByteArray);  
  70.     }  
  71.   
  72.     private static string DecryptIndirectReferenceValue(  
  73.         byte[] indirectReferenceByteArray)  
  74.     {  
  75.         byte[] decryptedByteArray;  
  76.         using (SymmetricAlgorithm algorithm = GetAlgorithm())  
  77.         {  
  78.             var encryptedByteArray = new byte[OutputByteSize];  
  79.             var iv = new byte[IvSize];  
  80.   
  81.             //separate off the actual encrypted value and the IV from the byte array  
  82.             Buffer.BlockCopy(  
  83.                 indirectReferenceByteArray,   
  84.                 0,   
  85.                 encryptedByteArray,   
  86.                 0,   
  87.                 OutputByteSize);  
  88.                   
  89.             Buffer.BlockCopy(  
  90.                 indirectReferenceByteArray,   
  91.                 encryptedByteArray.Length,   
  92.                 iv,   
  93.                 0,   
  94.                 IvSize);  
  95.   
  96.             //decrypt the byte array using the IV that was stored with the value  
  97.             decryptedByteArray = GetDecryptedByteArray(algorithm, iv, encryptedByteArray);  
  98.         }  
  99.         //decode the UTF8 encoded byte array  
  100.         return Encoding.UTF8.GetString(decryptedByteArray);  
  101.     }  
  102.   
  103.     private static byte[] GetDecryptedByteArray(  
  104.          SymmetricAlgorithm algorithm, byte[] iv, byte[] valueToBeDecrypted)  
  105.     {  
  106.         var decryptor = algorithm.CreateDecryptor(Key, iv);  
  107.         return decryptor.TransformFinalBlock(  
  108.             valueToBeDecrypted, 0, valueToBeDecrypted.Length);  
  109.     }  
  110.   
  111.     private static byte[] GetEncrptedByteArray(  
  112.         SymmetricAlgorithm algorithm, byte[] iv, byte[] valueToBeEncrypted)  
  113.     {  
  114.         var encryptor = algorithm.CreateEncryptor(Key, iv);  
  115.         return encryptor.TransformFinalBlock(  
  116.             valueToBeEncrypted, 0, valueToBeEncrypted.Length);  
  117.     }  
  118.           
  119.     private static AesManaged GetAlgorithm()  
  120.     {  
  121.         var aesManaged = new AesManaged  
  122.         {  
  123.             KeySize = KeySize,   
  124.             Mode = CipherMode.CBC,   
  125.             Padding = PaddingMode.PKCS7  
  126.         };  
  127.         return aesManaged;  
  128.      }  
  129.   
  130.      private static byte[] GetRandomValue()  
  131.      {  
  132.         var csprng = new RNGCryptoServiceProvider();  
  133.         var buffer = new Byte[16];  
  134.   
  135.         //generate the random indirect value  
  136.         csprng.GetBytes(buffer);  
  137.         return buffer;  
  138.      }  
  139. }   
Here our API should look the same as the ScopedReferenceMap, it's only what it is doing internally that has changed.  We're leveraging the .NET AesManaged symmetric encryption library with a 128 bit key and a cryptographically strong random value for the initialization vector (IV). Some of you might have recognized, but this is optimized for speed as opposed to strength. How is that?

AesManagedis ~170x  quicker to instantiate at as opposed to the FIPS equivalent AesCryptoServiceProvider.

128 length does less 4 rounds of the algorithm less than the larger 256.
 
One of the key points is that we generate a cryptographically strong random value for the initialization vector (IV) for every process of the encryption process.  The key is also the secret, to be kept secret, I have opted to leave it up to you to figure out how you want to pull in the key. But the nice part is that we don't need to share the key with any other party. Finally, we need to store the non-secret IV with the cipher (indirect reference) so that we can decrypt the indirect reference on a request.

Now, this is also a less complex approach.  An improved solution would be to use Authenticated Encryption (AE) that would include the same preceding process but with an applied Hash-based Message Authentication Code process.  Authenticated Encryption would also shore up security vulnerabilities exposed in areas such as padding and message tampering.  In addition, experts like Stan Drapkin will tell you that symmetric encryption must always be authentication encryption.

However, this isn't an article on encryption.  The entire point of providing this last option is to suggest other options for providing obfuscation to sensitive data for scenarios that don't quite work with a scoped indirect reference such as using .NET user sessions.
 
Keep These in Mind
  • The absolute most important take away from insecure and direct reference objects is that the only sure mitigation is having proper access controls in place. Any amount of obfuscation is not going to protect from unauthorized access to data.
  • Information is power and a malicious user can use it to their advantage in ways that you didn't anticipate until it's too late.  Therefore, when you determine that there is sensitive data that you need to apply a level of obfuscation be aware of the limitations to techniques such as using user sessions.  There is an overhead with .NET sessions so be aware of how you're utilizing it.
  • Most applications will not require obfuscation and the creation of indirect references.  Financial and similar highly sensitive sites would be better candidates for this additional layer of security.
  • That last point leads to this; obfuscation of specific data keys is only secure using obscurity and is intended to work in conjunction with other mitigations such as proper access controls.  In the context of this topic, it should not be relied on by itself.

Conclusion

Insecure Direct Object Reference is primarily about securing data from unauthorized access using proper access controls. Secondarily, knowing when and how to avoid leaking sensitive data from our application such as direct keys by applying a level of obfuscation using indirect references to those keys.  Finally, be aware of the limitations to indirect reference map mitigations when making the decisions to apply obfuscation techniques.

Up Next
    Ebook Download
    View all
    Learn
    View all