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:
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 ControlTherefore, 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.
- [Authorize(Roles = "Admin")]
- public class AccountsController : Controller
- {
-
-
- }
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:
- [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 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.
ObfuscationObfuscation 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 MapA 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:
- public static class ScopedReferenceMap
- {
- private const int Buffer = 32;
-
-
-
-
-
-
-
- public static string GetIndirectReference<T>(this T value)
- {
-
- 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);
- }
-
-
-
-
-
-
-
- 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 (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];
-
-
- csprng.GetBytes(buffer);
-
-
- 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 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:
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)
- {
-
- var directRefstr = accountNumber.GetDirectReference();
- var accountNum = Convert.ToInt64(directRefstr);
-
- Account account = _accountRepository.Find(accountNum);
-
-
- 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 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 HateIf 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 MapTo 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/99933In 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;
- public const int IvSize = 16;
- public const int OutputByteSize = KeySize / 8;
- private static readonly byte[] Key;
-
- static StaticReferenceMap()
- {
- Key =
- }
-
-
-
-
-
-
-
-
- public static string GetIndirectReferenceMap<T>(this T value)
- {
-
- var converter = TypeDescriptor.GetConverter(typeof (T));
- if (!converter.CanConvertTo(typeof (string)))
- {
- throw new ApplicationException("Can't convert value to string");
- }
-
-
- var directReferenceStr = converter.ConvertToString(value);
-
-
- var directReferenceByteArray = Encoding.UTF8.GetBytes(directReferenceStr);
-
-
- var urlSafeToken = EncryptDirectReferenceValue<T>(directReferenceByteArray);
- return urlSafeToken;
- }
-
-
-
-
-
-
-
- public static string GetDirectReferenceMap(this string indirectReference)
- {
- var indirectReferenceByteArray =
- HttpServerUtility.UrlTokenDecode(indirectReference);
- return DecryptIndirectReferenceValue(indirectReferenceByteArray);
- }
-
- private static string EncryptDirectReferenceValue<T>(byte[] directReferenceByteArray)
- {
-
- var iv = GetRandomValue();
-
-
- 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];
-
-
- Buffer.BlockCopy(
- indirectReferenceByteArray,
- 0,
- encryptedByteArray,
- 0,
- OutputByteSize);
-
- Buffer.BlockCopy(
- indirectReferenceByteArray,
- encryptedByteArray.Length,
- iv,
- 0,
- IvSize);
-
-
- decryptedByteArray = GetDecryptedByteArray(algorithm, iv, encryptedByteArray);
- }
-
- 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];
-
-
- 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?
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