Mastodon

OWASP Top 10 for .NET developers part 4: Insecure direct object reference

Consider for a moment the sheer volume of information that sits out there on the web and is accessible by literally anyone. No authentication required, no subversive techniques need be employed, these days just a simple Google search can turn up all sorts of things. And yes, that includes content which hasn’t been promoted and even content which sits behind a publicly facing IP address without a user-friendly domain name.

Interested in confidential government documents? Here you go. How about viewing the streams from personal webcams? This one’s easy. I’ll hasten a guess that in many of these scenarios, people relied on the good old security through obscurity mantra. If I don’t tell anyone it’s there, nobody will find it, right?

Wrong, very wrong and unfortunately this mentality persists well beyond just document storage and web cams, it’s prevalent in application design. Developers often implement solutions with the full expectation it will only ever be accessed in the intended context, unaware (or unconcerned) that just a little bit of exploration and experimenting can open some fairly major holes in their app.

Defining insecure direct object reference

Put very simply, direct object reference vulnerabilities result in data being unintentionally disclosed because it is not properly secured. In application design terms, this usually means pages or services allow requests to be made to specific objects without the proper verification of the requestor’s right to the content.

OWASP describes it as follows in the Top 10:

A direct object reference occurs when a developer exposes a reference to an internal implementation object, such as a file, directory, or database key. Without an access control check or other protection, attackers can manipulate these references to access unauthorized data.

In this scenario, the object we’re referring to is frequently a database key which might be exposed somewhere in a fashion where it is able to be manipulated. Commonly this will happen with query strings because they’re highly visible and manipulation is easy but it could just as easily be contained in post data.

Let’s look at how OWASP defines how people get in and exploit the vulnerability and what the impact of that might be:

Threat
Agents
Attack
Vectors
Security Weakness Technical
Impacts
Business
Impact
  Exploitability
EASY
Prevalence
COMMON
Detectability
EASY
Impact
MODERATE
 
Consider the types of users of your system. Do any users have only partial access to certain types of system data? Attacker, who is an authorized system user, simply changes a parameter value that directly refers to a system object to another object the user isn’t authorized for. Is access granted?

Applications frequently use the actual name or key of an object when generating web pages. Applications don’t always verify the user is authorized for the target object. This results in an insecure direct object reference flaw. Testers can easily manipulate parameter values to detect such flaws and code analysis quickly shows whether authorization is properly verified.

Such flaws can compromise all the data that can be referenced by the parameter. Unless the name space is sparse, it’s easy for an attacker to access all available data of that type.

Consider the business value of the exposed data.
Also consider the business impact of public exposure of the vulnerability.

This explanation talks a lot about parameters which are a key concept to understand in the context of direct object vulnerabilities. Different content is frequently accessible through the same implementation, such as a dynamic web page, but depending on the context of the parameters, different access rules might apply. Just because you can hit a particular web page doesn’t mean you should be able to execute it in any context with any parameter.

Anatomy of insecure direct object references

In its essence, this is a very simple vulnerability to understand; it just involves requesting content you’re not authorised to access by manipulating the object reference. Rather than dumbing this example down too much as I have with previous, more complex OWASP risks, let’s make this a little more real world and then I’ll tie it back into some very specific real world incidents of the same nature.

Let’s imagine we have an ASP.NET webpage which is loaded once a user is authenticated to the system. In this example, the user is a customer and one of the functions available to them is the ability to view their customer details.

To give this a bit of a twist, the process of retrieving customer details is going to happen asynchronously using AJAX. I’ve implemented it this way partly to illustrate the risk in a slightly less glaringly obvious fashion but mostly because more and more frequently, AJAX calls are performing these types of data operations. Particularly with the growing popularity of jQuery, we’re seeing more and more services being stood up with endpoints exposed to retrieve data, sometimes of a sensitive nature. This creates an entirely new attack vector so it’s a good one to illustrate here.

Here’s how the page looks (I’ve started out with the base Visual Studio 2010 web app hence the styling):

image

After clicking the button, the customer details are returned and written to the page:

image

Assuming we’re an outside party not (yet) privy to the internal mechanism of this process, let’s do some discovery work. I’m going to use Firebug Lite for Google Chrome to see if this is actually pulling data over HTTP or simply populating it from local variables. Hitting the button again exposes the following information in Firebug:

image

Here we can see that yes, the page is indeed making a post request to an HTTP address ending in CustomerService.svc/GetCustomer. We can also see that a parameter with the name “customerId” and value “3” is being sent with the post.

If we jump over to the response tab, we start to see some really interesting info:

{"d":{"__type":"Customer:#Web","Address":"3 Childers St","CustomerID":3,"Email":"brucec@aol.com","FirstName":"Bruce","Postcode":"3000","State":"VIC","Suburb":"Melbourne"}}

Here we have a nice JSON response which shows that not only are we retrieving the customer’s name and email address, we’re also retrieving what appears to be a physical address. But so far, none of this is a problem. We’ve legitimately logged on as a customer and have retrieved our own data. Let’s try and change that.

What I now want to do is re-issue the same request but with a different customer ID. I’m going to do this using Fiddler which is a fantastic tool for capturing and reissuing HTTP requests. First, I’ll hit the “Get my details” button again and inspect the request:

image

Here we see the request to the left of the screen, the post data in the upper right and the response just below it. This is all consistent with what we saw in Firebug, let’s now change that.

I’ve flicked over to the “Request Builder” tab then dragged the request from the left of the screen onto it. What we now see is the request recreated in its entirety, including the customer ID. I’m going to update this to “4”:

image

With a new request now created, let’s hit the “Execute” button then switch back to the inspectors view and look at the response:

image

When we look at the response, we can now clearly see a different customer has been returned complete with their name and address. Because the customer ID is sequential, I could easily script the request and enumerate through n records retrieving the private data of every customer in the system.

Bingo. Confidential data exposed.

What made this possible?

What should now be pretty apparent is that I was able to request the service retrieving another customer’s details without being authorised to do so. Obviously we don’t want to have a situation where any customer (or even just anyone who can hit the service) can retrieve any customer’s details. When this happens, we’ve got a case of an insecure direct object reference.

This exploit was made even easier by the fact that the customer’s ID was an integer; auto-incrementing it is both logical and straight forward. Had the ID been a type that didn’t hold predictable values, such as a GUID, it would have been a very different exercise as I would had to have known the other customer’s ID and could not have merely guessed it. Having said that, key types are not strictly what this risk sets out to address but it’s worth a mention anyway.

Implementing access control

Obviously the problem here was unauthorised access and the solution is to add some controls around who can access the service. The host page is fundamentally simple in its design:

Register a script manager with a reference to the service:

<asp:ScriptManager runat="server">
  <Services>
    <asp:ServiceReference Path="CustomerService.svc" />
  </Services>
</asp:ScriptManager>

Add some intro text and a button to fire the service call:

<p>You can retrieve your customer details using the button below.</p>
<input type="button" value="Get my details" onclick="return GetCustomer()" />

Insert a few lines of JavaScript to do the hard work:

<script language="javascript" type="text/javascript">
// <![CDATA[
  function GetCustomer() {
    var service = new Web.CustomerService();
    service.GetCustomer(<%= GetCustomerId() %>, onSuccess, null, null);
  }

function onSuccess(result) {
document.getElementById('customerName').innerHTML = result.FirstName;
document.getElementById('customerEmail').innerHTML = result.Email;
document.getElementById('customerDetails').style.visibility = 'visible';
}
// ]]>
</script>

Note: the first parameter of the GetCustomer method is retrieved dynamically. The implementation behind the GetCustomerId method is not important in the context of this post, although it would normally be returned based on the identity of the logged on user.

And finally, some controls to render the output to:

<div id="customerDetails" style="visibility: hidden;">
<
h2>My details</h2>
Name: <span id="customerName"></span><br />
Email: <span id="customerEmail"></span>
</
div>

No problems here, all of this is fine as we’re not actually doing any work with the customer details. What we want to do is take a look inside the customer service. Because we adhere to good service orientated architecture principals, we’re assuming the service is autonomous and not tightly coupled to any single implementation of it. As such, the authorisation work needs to happen within the service.

The service is just a simple AJAX-enabled WCF service item in the ASP.NET web application project. Here’s how it looks:

[OperationContract]
public Customer GetCustomer(int customerId)
{
var dc = new InsecureAppDataContext();
return dc.Customers.Single(e => e.CustomerID == customerId);
}

There are a number of different ways we could secure this; MSDN magazine has a nice overview of Authorisation in WCF-Based Services which is a good place to start. There are a variety of elegant mechanisms available closely integrated with the authorisation model of ASP.NET pages but rather than going down that route and introducing the membership provider into this post, let’s just look at a bare basic implementation:

[OperationContract]
public Customer GetCustomer(int customerId)
{
if (!CanCurrentUserAccessCustomer(customerId))
{
throw new UnauthorizedAccessException();
}

var dc = new InsecureAppDataContext();
return dc.Customers.Single(e => e.CustomerID == customerId);
}

That’s it. Establish an identity, validate access rights then run the service otherwise bail them out. The implementation behind the CanCurrentUserAccessCustomer method is inconsequential, the key message is that there is a process validating the user’s right access the customer data before anything is returned.

Using an indirect reference map

A crucial element in the exploit demonstrated above is that the internal object identifier – the customer ID – was both exposed and predictable. If we didn’t know the internal ID to begin with, the exploit could not have occurred. This is where indirect reference maps come into play.

An indirect reference map is simply is simply a substitution of the internal reference with an alternate ID which can be safely exposed externally. Firstly, a map is created on the server between the actual key and the substitution. Next, the key is translated to its substitution before being exposed to the UI. Finally, after the substituted key is returned to the server, it’s translated back to the original before the data is retrieved.

The access reference map page on OWASP gives a neat visual representation of this:

image

Let’s bring this back to our original app. We’re going to map the original customer ID integer to a GUID, store the lookup in a dictionary and then persist it in a session variable. The data type isn’t particularly important so long as it’s unique, GUIDs just make it really easy to generate unique IDs. By keeping it in session state we keep the mapping only accessible to the current user and only in their current session.

We’ll need two publicly facing methods; one to get a direct reference from the indirect version and another to do the reverse. We’ll also add a private method to create the map in the first place:

public static class IndirectReferenceMap
{
public static int GetDirectReference(Guid indirectReference)
{
var map = (Dictionary<Guid, int>)HttpContext.Current.Session["IndirMap"];
return map[indirectReference];
}

public static Guid GetIndirectReference(int directReference)
{
var map = (Dictionary<int, Guid>)HttpContext.Current.Session["DirMap"];
return map == null ?
AddDirectReference(directReference)
: map[directReference];
}

private static Guid AddDirectReference(int directReference)
{
var indirectReference = Guid.NewGuid();
HttpContext.Current.Session["DirMap"] = new Dictionary<int, Guid>
{ {directReference, indirectReference } };
HttpContext.Current.Session["IndirMap"] = new Dictionary<Guid, int>
{ {indirectReference, directReference } };
return indirectReference;
}
}

This is pretty fast and easy – it won’t handle scenarios such as trying the get the direct reference before the map is created or handle any other errors that occur – but it’s a good, simple implementation to demonstrate the objective. All we need to do now is translate the reference backwards and forwards in the appropriate places.

First we create it when constructing the AJAX syntax to call the service (it’s now a GUID hence the encapsulation in quotes):

service.GetCustomer('<%= IndirectReferenceMap.
GetIndirectReference(GetCustomerId())
%>', onSuccess, null, null);

Then we map it back in the service definition. We need to change the method signature (the ID is now a GUID), then translate it back to the original, direct reference before going any further:

public Customer GetCustomer(Guid indirectId)
{
var customerId = IndirectReferenceMap.GetDirectReference(indirectId);

Once we do this, the AJAX request looks like this:

image

Substituting the customerId parameter for any other value won’t yield a result as it’s now an indirect reference which needs a corresponding map in the dictionary stored in session state. Even if the actual ID of another customer was known, nothing can be done about it.

Avoid using discoverable references

This approach doesn’t tend to make it into most of the published mitigation strategies for insecure direct object references but there’s a lot to be said for avoiding “discoverable” reference types. The original coded example above was exploited because the object reference was an integer and it was simply incremented then passed back to the service. The same could be said for natural keys being used as object references; if they’re discoverable, you’re one step closer to an exploit.

This approach could well be viewed as another example of security through obscurity and on its own, it would be. The access controls are absolutely essential and an indirect reference map is another valuable layer of defence. Non-discoverable references are a not a replacement for either of these.

The fact is though, there are other good reasons for using object references such as GUIDs in a system design. There are also arguments against them (i.e. the number of bytes they consume), but where an application implements a globally unique reference pattern there is a certain degree of implicit security that comes along with it.

Hacking the Australian Tax Office

The ATO probably doesn’t have a lot of friends to begin with and there may not have been a lot of sympathy for them when this happened, but this is a pretty serious example of a direct object reference gone wrong. Back in 2000 when we launched the GST down under, the ATO stood up a website to help businesses register to collect the new tax. An inquisitive user (apparently) inadvertently discovered a major flaw in the design:

I worked out pretty much how the site was working and it occurred to me that I could manipulate the site to reveal someone else's details.

I found that quite shocking, so I decided to send everyone who was affected an email to tell them about that.

The email he sent included the bank account details and contact phone numbers for the recipients. He was able to breach the ATO’s security by observing that URLs contained his ABN – Australian Business Number – which is easily discoverable for any registered company in the country. Obviously this value was then manipulated and as we saw in the example above, someone else’s details were returned.

Obviously the ATO was both using the ABN as a direct object reference and not validating the current user’s rights to access the underlying object. But beyond this, they used an easily discoverable, natural reference rather than a surrogate. Just like in my earlier example with the integer, discoverable references are an important part of successfully exploiting insecure direct object reference vulnerabilities.

Insecure direct object reference, Apple style

Just in case the potential ramifications of this risk aren’t quite clear, let’s take a look at what happened with the launch of the iPad in the US earlier on in the year. In a case very similar to the vulnerability I demonstrated above, Apple had 114,000 customer’s details exposed when they rolled out the iPad. Actually, in all fairness, it was more a vulnerability on behalf of AT&T, the sole carrier for Apple 3G services in the US.

What appears to have happened in this case is that a service has been stood up to resolve the customer’s ICC-ID (an identifier stored on the SIM card), to the corresponding owner’s email address. Pass the service the ID, get the email address back.

The problem with this, as we now know, is that if the ID is sequential (as an ICC-ID is), other IDs are easily guessed and passed to the service. If the service is not appropriately secured and allows direct access to the underlying object – in this case, the customer record – we have a vulnerability.

One interesting point the article makes is that the malicious script “had to send an iPad-style User agent header in their Web request”. Assumedly, AT&T’s service had attempted to implement a very rudimentary security layer by only allowing requests which passed a request header stating the user agent was an iPad. This value is nothing more than a string in the request header and as we can see in the Fiddler request we created earlier on, it’s clearly stated and easily manipulated to any value the requestor desires:

image

The final thing to understand from the iPad / AT&T incident is that as innocuous as it might seem, this is a serious breach with very serious repercussions. Yes, it’s only email addresses, but its disclosure is both an invasion of privacy and a potential risk to the person’s identity in the future. If you’re in any doubt of the seriousness of an event like this, this one sentence should put it in perspective:

The FBI has confirmed that it has opened an investigation into the iPad breach and Gawker Media can confirm that it has been contacted by the agency.

Insecure direct object reference v. information leakage contention

There is some contention that events such as this are more a matter of information leakage as opposed to insecure direct object references. Indeed OWASP’s previous Top 10 from 2007 did have “Information Leakage and Improper Error Handling” but it didn’t make the cut for 2010. In this post there’s an observation from Jeremiah Grossman to the effect of:

Information leakage is not a vulnerability, but the effects of an exploited vulnerability. Many of the OWASP Top 10 may lead to information leakage.

The difference is probably a little semantic, at least in the context of demonstrating insecure code, as the effect is a critical driver for addressing the cause. The comments following the link above demonstrate sufficient contention that I’m happy to sit on the fence, avoid the pigeon holing and simply talk about how to avoid it – both the vulnerability and the fallout – through writing secure code.

Summary

This risk is another good example of where security needs to get applied in layers as opposed to just a single panacea attempting to close the threat door in one go. Having said that, the core issue is undoubtedly the access control because once that’s done properly, the other defences are largely redundant.

The discoverable references suggestion is one of those religious debates where everyone has their own opinion on natural versus surrogate keys and when the latter is chosen, what type it should be. Personally, I love the GUID where its length is not prohibitive to performance or other aspects of the design because it has so many other positive attributes.

As for indirect reference maps, they’re a great security feature, no doubt, I’d just be a little selective about where they’re applied. There’s a strong argument for them in say, the banking sector, but I’d probably skip the added complexity burden in less regulated environments in deference to getting that access control right.

The reason things went wrong for the ATO and for AT&T is that they simply screwed up every single layer! If the Aussie tax office and the largest mobile carrier in the US can make this mistake, is it any wonder this risk is so pervasive?!

Resources

  1. ESAPI Access Reference Map
  2. Insecure Direct Object Reference
  3. Choosing a Primary Key: Natural or Surrogate?
  4. 10 Reasons Websites get hacked

OWASP Top 10 for .NET developers series

1. Injection
2. Cross-Site Scripting (XSS)
3. Broken Authentication and Session Management
4. Insecure Direct Object References
5. Cross-Site Request Forgery (CSRF)

6. Security Misconfiguration
7. Insecure Cryptographic Storage
8. Failure to Restrict URL Access
9. Insufficient Transport Layer Protection
10. Unvalidated Redirects and Forwards

Security .NET OWASP
Tweet Post Update Email RSS

Hi, I'm Troy Hunt, I write this blog, create courses for Pluralsight and am a Microsoft Regional Director and MVP who travels the world speaking at events and training technology professionals