Now my DevSecNerdRage™ has calmed down I thought it might be guess at what went wrong with Citibank and how you, as a developer, can avoid making the same mistake. From reports in the New York Times it appears the attackers had a valid login and password for a Citibank credit card site and once in they changed the account number in the URL. This is a great example of Insecure Direct Object Access.

I can imagine the code / configuration doing one thing; (all code in this post is pseudo code)

if (IsAuthenticated)
{
  LoadAccount(Request.QueryString["accountNumber"]);
}


Its not the first time this has happened on a high profile system – a few years back junior doctors had their information exposed by the system they were using to apply for placements, MTAS, in much the same way. Rather than just account numbers this exposed criminal convictions, phone numbers and even sexual orientation.

So how to we approach fixing this? First let’s secure the direct object reference. Right now at work I’m rewriting a system which controls SSL Certificate issuance within Microsoft. Each request has a request ID, and the URLs work as you would expect, http://example.com/create, http://example.com/view?id=1 and so on. Each database record has a CreatedBy field, and so when someone tries to view the record I do something like

CertificateRequest certificateRequest = repository.GetRequest(id)
if (certificateRequest == null)
{
    RedirectToAction("Not Found");
}
if (certificateRequest.CreatedBy != User.Identity.Name)
{
    // Unauthorized
    // Set off big whooping sirens and flashing lights
    RedirectToAction("Unauthorized");
}


That’s fine for my little system, but perhaps not for a bank, or an extranet exposing orders. Why not? Well I could write a crawler that starts with an id field of 1 and counts upwards. Even if I wasn’t authenticated I’d get an Unauthorized result if I hit a valid request. This would enable me to work out how many orders, or accounts are in a system. This sort of information leak in an order system might be interesting to a competitor – all they do is keep incrementing until they get a “not found” error and now they know how many orders your company has taken. With a credit card system you could use this to discover active account numbers if you have different results for unauthorized and not found errors.

So we need to change the direct object reference to an indirect object reference. An indirect object reference is a non-primary, non-sequential key, which resolves back to the object you want. The usual way to do this is to have a mapping column in your record, which you guarantee is unique and non-sequential. If you’re thinking of a GUID right now beware – guids issued quickly, one after the other have a high degree of predictability, you could go from d129b4ad-212f-4d7d-93a6-0d681efa4793 to d129b4ad-212f-4d7e-93a6-0d681efa4793 easily, especially if you’re fixing up an existing table and adding a new column for an indirect reference and let SQL generate them as a default value for existing rows. If that is the case, add the column, then have a little command line program or stored procedure which goes through your table line by line and pauses for a random amount of time, generates the GUID, and then inserts it.

However we can get better security than this. The perfect indirect object reference is unique on a per user basis. If, for example, I have three accounts with my bank, and I have a drop down to switch between them then I could do the following:

<select>
  <option value="1">Checking</option>
  <option value="2">Savings</option>
  <option value="3">Credit Card</option>
</select>

 

Now we have unique, per user, identifiers. Our loading code would become something like

AccountDetails accountDetails = repository.GetAccountDetails(id, User.Identity.Name)
if (accountDetails == null)
{
    RedirectToAction("Not Found");
}
 


We’ve taken away the direct object reference and made the account dependant on an indirect object reference which is user specific. The ownership check has become a core part of loading the account. If you’re worried about an attacker working out how many accounts a user has in this scenario remember that in order to get to the page they’re going to have to have valid credentials, so they can see the drop down anyway.

In addition to all of this if you’re passing these via query strings you could use tamper-proof linking, where you create an HMAC of the parameters you want to protect and append that as a parameter to the URL, validating it when the new page is loaded.

Insecure Direct Object Reference is covered in the SDL Threat Modelling tool and is easily discovered in code reviews. Yes both threat modelling and code reviews have a security tax associated with them, they do take additional time, but as you do them and get used to them you’ll find the tax becomes smaller and smaller. It will never go away, but then do you want to be caught up and embarrassed as badly as Citibank was this week?