Insecure Direct Object Reference: Is Your ASP.NET App Data Secure? - CodeProject

:

Introduction

As an aerospace engineer at X94, your boss has asked you to retrieve the technical drawings on a particular patent from building 2. Unfortunately, access to the building requires you producing proof you have access to the building which you promptly do 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 which 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 above 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 documents identity in the form of a alpha-numeric code which indirectly mapped to the actual document’s identification and location within the library.

From a figurative point, this analogy is the answer to a prevalent web application security flaw referred to as “Insecure Direct Object Reference” and 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; user’s making requests to see product details, adding products to their shopping cart or some other similar activity. Most likely, you leveraged the product’s ID to identify the product the user was requesting to see the details of, add to their cart, etc. 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 which was a direct reference to the same identifier in the database.

“Yeah, so what?” Well, for the simple business to customer scenarios, the above 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 on your accounts page, you have selected to see the details of your savings account with ID 1344573490:

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

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 to 1344573490 to 1344573491?

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. In this example, we have a direct reference of an account which can be identified anywhere within the system by the account number. Furthermore, we are exercising a potential problem of directly referencing an account which 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 be more clear of what this flaw is really describing:

 

 

If insecure direct object reference is a case of both...

  1. Leaking sensitive data and
  2. Lack of proper access controls

...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. When we think about securing resources within our web application, we can approach it with the same principle in mind.

Route Access Control

First, 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.

[Authorize]
public class AccountsController : Controller
{
    [HttpGet]
    public ActionResult Details(long accountNumber)
    {
        //...

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:

[Authorize(Roles = "Admin, Manager")]
 public class AccountsController : Controller
 {
     //..

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 different 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 the 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 are 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.

[Authorize(Roles = "Admin")]
public class AccountsController : Controller
{
    //..

However, more often you are going to be required to validate authorization at the data level by ensuring the user has access to the requested resource. Depending on many different 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:

[Authorize]
public class AccountsController : Controller
{
   [HttpGet]
   public ActionResult Details(long accountNumber)
   {
       Account account = _accountRepository.Find(accountNumber);
       if (account.UserId != User.Identity.GetUserId())
       {
           return new HttpUnauthorizedResult("User is not Authorized.");
       }
       //

Recall we have the [Authorize] attribute applied at the controller level and don’t have to be redundant at the action level.

It is important to note, that in the above example of issuing an Unauthorized Result with the use of Forms Authentication in ASP.NET will force a 302 redirect to the login page, despite whether the user is 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 framework flavor, 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 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 mentioned on 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 implication of insecure direct object reference, obfuscation.

 

If your liking this, follow me on Twitter 

Obfuscation

Obfuscation is the deliberate act to hide the intended meaning. In our case, we can use obfuscation as a means for inferring security through obscurity. A simple example people can identify with is shortened URLs. Though security is not the intent, a URL such as http://bit.ly/1Gg2Pnn is obfuscated from what the actual URL represents. Following this 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. While 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 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 different 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. In order 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 which is our account number. The map the server uses is stored in a ASP.NET user session, which becomes the scope. More on the scope in a minute, let’s look at an implementation:

public static class ScopedReferenceMap
{
    private const int Buffer = 32;

    /// <summary>
    /// Extension method to retrieve a public facing indirect value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string GetIndirectReference<T>(this T value)
    {
        //Get a converter to convert value to string
        var converter = TypeDescriptor.GetConverter(typeof (T));
        if (!converter.CanConvertTo(typeof (string)))
        {
            throw new ApplicationException("Can't convert value to string");
        }

        var directReference = converter.ConvertToString(value);
        return CreateOrAddMapping(directReference);
    }

    /// <summary>
    /// Extension method to retrieve the direct value from the user session
    /// if it doesn't exists, the session has ended or this is possibly an attack
    /// </summary>
    /// <param name="indirectReference"></param>
    /// <returns></returns>
    public static string GetDirectReference(this string indirectReference)
    {
        var map = HttpContext.Current.Session["RefMap"];
        if (map == null ) throw new ApplicationException("Can't retrieve direct reference map");

        return ((Dictionary<string, string>) map)[indirectReference];
    }

    private static string CreateOrAddMapping(string directReference)
    {
        var indirectReference = GetUrlSaveValue();
        var map =
           (Dictionary<string, string>) HttpContext.Current.Session["RefMap"] ??
                    new Dictionary<string, string>();

        //If we have it, return it.
        if (map.ContainsKey(directReference)) return map[directReference];


        map.Add(directReference, indirectReference);
        map.Add(indirectReference, directReference);

        HttpContext.Current.Session["RefMap"] = map;
        return indirectReference;
    }

    private static string GetUrlSaveValue()
    {
        var csprng = new RNGCryptoServiceProvider();
        var buffer = new Byte[Buffer];

        //generate the random indirect value
        csprng.GetBytes(buffer);

        //base64 encode the random indirect value to a URL safe transmittable value
        return HttpServerUtility.UrlTokenEncode(buffer);
    }
} 

Here, we have created a simple utility class ScopedReferenceMap that provides extension methods to a value like our bank account 1344573490 into Xvqw2JEm84w1qqLN1vE5XZUdc7BFqarB0 which is our indirect reference.

Ultimately, when an indirect reference value is requested, we use a user’s session as a means 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 performed on a valid and specific user session.

You can utilize it wherever you need to create the indirect reference like your model or view:

AccountNumber = accountNumber.GetIndirectReference(); //create an indirect reference

Now, on an incoming request for the details to an account using the following URL:

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:

[HttpGet]
public ActionResult Details(string accountNumber)
{
    //get direct reference
    var directRefstr = accountNumber.GetDirectReference();
    var accountNum = Convert.ToInt64(directRefstr);

    Account account = _accountRepository.Find(accountNum);

    //Verify authorization
    if (account.UserId != User.Identity.GetUserId())
    {
        return new HttpUnauthorizedResult("User is not Authorized.");
    }

    //

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 performed. However, if the map exists but it finds the direct reference then it is possible the value has been tampered with.

As I mentioned, 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, maybe you have issues with using 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 (e.g. 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 on HATEOAS is the ability for web services to provide discoverable actions on a resource which 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 you’re going to need to look to other resources for an understanding.

Therefore, the idea of providing a URL that contains scoped indirect reference parameters is a problem with concepts like HATEOAS or anytime we need to provide durable URL’s (URL’s that need to have a longer time-to-live). We have to 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

In order to provide durable URLs that contain indirect references, we are going to 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 IDs 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.

e.g. 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:

public static class StaticReferenceMap
{
    public const int KeySize = 128; //bits
    public const int IvSize = 16; //bytes
    public const int OutputByteSize = KeySize / 8;
    private static readonly byte[] Key;

    static StaticReferenceMap()
    {
        Key = //pull 128 bit key in
    }

    /// <summary>
    /// Generates an encrypted value using symmetric encryption.
    /// This is utilizing speed over strength due to the limit of security through obscurity
    /// </summary>
    /// <typeparam name="T">Primitive types only</typeparam>
    /// <param name="value">direct value to be encrypted</param>
    /// <returns>Encrypted value</returns>
    public static string GetIndirectReferenceMap<T>(this T value)
    {
        //Get a converter to convert value to string
        var converter = TypeDescriptor.GetConverter(typeof (T));
        if (!converter.CanConvertTo(typeof (string)))
        {
           throw new ApplicationException("Can't convert value to string");
        }

        //Convert value direct value to string
        var directReferenceStr = converter.ConvertToString(value);

        //encode using UT8
        var directReferenceByteArray = Encoding.UTF8.GetBytes(directReferenceStr);

        //Encrypt and return URL safe Token string which is the indirect reference value
        var urlSafeToken = EncryptDirectReferenceValue<T>(directReferenceByteArray);
        return urlSafeToken;
    }

    /// <summary>
    /// Give a encrypted indirect value, will decrypt the value and
    /// return the direct reference value
    /// </summary>
    /// <param name="indirectReference">encrypted string</param>
    /// <returns>direct value</returns>
    public static string GetDirectReferenceMap(this string indirectReference)
    {
       var indirectReferenceByteArray =
            HttpServerUtility.UrlTokenDecode(indirectReference);
       return DecryptIndirectReferenceValue(indirectReferenceByteArray);
    }

    private static string EncryptDirectReferenceValue<T>(byte[] directReferenceByteArray)
    {
        //IV needs to be a 16 byte cryptographic stength random value
        var iv = GetRandomValue();

        //We will store both the encrypted value and the IV used - IV is not a secret
        var indirectReferenceByteArray = new byte[OutputByteSize + IvSize];
        using (SymmetricAlgorithm algorithm = GetAlgorithm())
        {
           var encryptedByteArray =
               GetEncrptedByteArray(algorithm, iv, directReferenceByteArray);

           Buffer.BlockCopy(
               encryptedByteArray, 0, indirectReferenceByteArray, 0, OutputByteSize);
           Buffer.BlockCopy(iv, 0, indirectReferenceByteArray, OutputByteSize, IvSize);
        }
        return HttpServerUtility.UrlTokenEncode(indirectReferenceByteArray);
    }

    private static string DecryptIndirectReferenceValue(
        byte[] indirectReferenceByteArray)
    {
        byte[] decryptedByteArray;
        using (SymmetricAlgorithm algorithm = GetAlgorithm())
        {
            var encryptedByteArray = new byte[OutputByteSize];
            var iv = new byte[IvSize];

            //separate off the actual encrypted value and the IV from the byte array
            Buffer.BlockCopy(
                indirectReferenceByteArray,
                0,
                encryptedByteArray,
                0,
                OutputByteSize);

            Buffer.BlockCopy(
                indirectReferenceByteArray,
                encryptedByteArray.Length,
                iv,
                0,
                IvSize);

            //decrypt the byte array using the IV that was stored with the value
            decryptedByteArray = GetDecryptedByteArray(algorithm, iv, encryptedByteArray);
        }
        //decode the UTF8 encoded byte array
        return Encoding.UTF8.GetString(decryptedByteArray);
    }

    private static byte[] GetDecryptedByteArray(
         SymmetricAlgorithm algorithm, byte[] iv, byte[] valueToBeDecrypted)
    {
        var decryptor = algorithm.CreateDecryptor(Key, iv);
        return decryptor.TransformFinalBlock(
            valueToBeDecrypted, 0, valueToBeDecrypted.Length);
    }

    private static byte[] GetEncrptedByteArray(
        SymmetricAlgorithm algorithm, byte[] iv, byte[] valueToBeEncrypted)
    {
        var encryptor = algorithm.CreateEncryptor(Key, iv);
        return encryptor.TransformFinalBlock(
            valueToBeEncrypted, 0, valueToBeEncrypted.Length);
    }

    private static AesManaged GetAlgorithm()
    {
        var aesManaged = new AesManaged
        {
            KeySize = KeySize,
            Mode = CipherMode.CBC,
            Padding = PaddingMode.PKCS7
        };
        return aesManaged;
     }

     private static byte[] GetRandomValue()
     {
        var csprng = new RNGCryptoServiceProvider();
        var buffer = new Byte[16];

        //generate the random indirect value
        csprng.GetBytes(buffer);
        return buffer;
     }
} 

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?

  1. AesManaged is ~170x quicker to instantiate at as opposed to the FIPS equivalent AesCryptoServiceProvider
  2. 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 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 have to share the key with any other party. Finally, we have to store the non-secret IV with the cipher (indirect reference) so that we can decrypt the indirect reference on a request.

To be absolutely clear, this is by no means a replacement to access controls. This can and should only be used in conjunction with proper access controls.

Now, this is also a less complex approach. An improved solution would be to use Authenticated Encryption (AE) which would include the same above 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 bring to light other options for providing obfuscation to sensitive data for scenarios that don’t quite work with scoped indirect reference such as using .NET user sessions.

Keep These in Mind

  1. The absolute most important take away from insecure and direct objects references 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.
  2. 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.
  3. Most applications are not going to require obfuscation and the creation of indirect references. Financial and similar highly sensitive sites would be better candidates for this additional layer of security.
  4. That last point leads to this; obfuscation of specific data keys is only security through obscurity and 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 through 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 through indirect references to those keys. Finally, be aware of the limitations to indirect reference map mitigations when making the decisions to apply obfuscation techniques.