Skip to content

CSRF, Crumbs, and Cookies.

Simon Willison held a talk showing off a cavalcade of security hacks that have plagued major sites in the recent past, along with the ways to ensure you don’t suffer from the same problems.

One particular attack, or rather, the solution to it, piqued my interest: Cross Site Request Forgery, a.k.a. CSRF, a.k.a. seasurf. Simon suggested the use of a ‘crumb’. Cristiano did some research and figured out that symfony, a PHP framework, uses the same strategy, so it seems to be a popular scheme.

I think its effectively broken.

I’ll explain here why I think this is below the fold. Comments are of course very welcome.

The solution to avoiding CSRF attacks is to make sure the form submit contains a piece of information which another site’s javascript code or server can’t possibly know about. Symfony’s strategy is to create a crumb by calculating MD5(sessionID + systemSalt + nameOfFormClass). I will just mention that its 2008, so MD5 seems bloody dumb at this point in time, but, for all MD5′s faults, its not the major problem I have with this scheme, and it could be easily fixed by just replacing it with SHA-256.

My problem is that its needlessly complex. You can JUST send the sessionID, unhashed. That’s all you need to do. If anything, that would be more secure, in fact.

 

Here’s why I think its as secure or better:

1. If the hacker knows the sessionID, for whatever reason, then you can do everything that you could do if the hacker had known the systemSalt and the name of the form class as well; he would simply log in as you, and the system would give him the full crumb. The systemSalt+nameOfFormClass therefore don’t seem to add a significant hurdle for a hacker to find the crumb; its like locking a door with 3 heavy locks, but putting 2 of the 3 keys in a lockbox right next to the door, which is locked with only the first key. Those two keys are just creating the illusion of security and making it harder for you to analyse how safe your door is. In other words, this crumb scheme looks complicated but fundamentally, if the sessionID is compromised, the crumb is compromised.

2. If the hacker gets a hold of the crumb without any other information, he cannot use this crumb to obtain the sessionID. Even if he has an MD5 rainbow table, he won’t get far, because he’d have to guess at the nameOfFormClass, and he’d somehow have to access the secret systemSalt which is stored on a file on the server. However, the premise that a hacker learns the crumb without learning the sessionID is flawed: That would never happen. The only way a hacker is going to obtain the crumb is by reading the traffic data between user and server, but if he’s reading that data, he can see the HTTP headers just as easily as the content body. The HTTP header contains the session ID in plain sight. Therefore, in the act which gives the attacker the crumb, the attacker also learns of the session ID. Therefore, the crumb’s scheme does not need to worry itself about hiding the sessionID.

3. It is impossible to finagle the sessionID into a request body on all browsers; the browsers will happily add a cookie header for you, but it does not add information to the content body. Therefore, the presence of the sessionID in the request content (form request, AJAX JSON call, or whatever other scheme you use to communicate between the client and the server) is sufficient to prove that the request originated either from the user (who knows his session ID), or from a page served up from your own site.

4. If the client just has to send the cookie in the body, then some simple client side javascript can take care of it; there’s no need for the server to send this information to the client. Therefore, if somehow the hacker got a hold of the server -> client data but not the client -> server data, he’d have even less information than with the server-sends-a-crumb scheme. However, a page which does not require javascript will need a server generated crumb. In this case there’s a small argument to obfuscate the sessionID somewhat. However, any one-way conversion will be enough. var x = random10DigitNumber(); var crumb = randomNumber + SHA256(randomNumber + sessionID); is much simpler and just as effective.

I therefore posit that convoluted crumb schemes are broken security in the sense that they give you a false sense of security and make it harder for you to analyse what you do have. For example, if you think that a crumb that includes, say, a timeout scheme so that requests have to be fulfilled within 30 minutes protects you against hackers which collect packet sniffer logs at the end of every day, you’d be wrong: The only way to protect against those is to have a scheme whereby the sessionID invalidates every 30 minutes (for example, the paypal tactic whereby the webapp asks you for your password every other page. Works, but make sure your windows are rotten fruit proof, as that tends to peeve your users quite a bit). The fact that the crumb invalidates every 30 minutes is irrelevant.
Discuss. No, seriously. This is security stuff; people tend to get this stuff wrong far more often than they get it right. Where’s the flaw in my argument?

{ 7 } Comments

  1. Mark Ng | 2008/10/02 at 18:38 | Permalink

    My immediate thought is that you’d have to be careful with cache-control headers if you were going to send the session ID unhashed in the page.

    A caching proxy won’t send you someone elses cookies, but it will send you someone elses page if it hasn’t been told it’s private. This would mean that someone could get your session ID by using the same proxy as you.

  2. cbetta | 2008/10/02 at 18:46 | Permalink

    @markng that same issue arrises for normal CSRF tokens, right? although those are just tokens, not entire session IDs

  3. Mark Ng | 2008/10/02 at 18:51 | Permalink

    @cbetta sure, but stealing a CSRF token in this context means that you have to steal the CSRF token from the proxy, identify the user that CSRF token belongs to (difficult, unless you know the proxy is only being used by a couple of people) and then persuade that user to visit a URL with your CSRF attack script in it.

    Much less easy than just stealing someones session ID and not having to know which user it is in advance.

  4. Mark Ng | 2008/10/02 at 18:52 | Permalink

    @cbetta also, of course, you’d be restricted to actions within that form, rather than actions across the whole site.

  5. cbetta | 2008/10/02 at 18:55 | Permalink

    True. Quite a good point there. Still means anyone doing CSRF should always think of setting the cache-control headers properly to prevent CSRF problems.

  6. rzwitserloot | 2008/10/03 at 02:37 | Permalink

    Good one, Mark. random+SHA256(random+sessionID) at the very least has a clear advantage over raw sessionIDs in the page proper.

    Tipit requires javascript at the moment so I don’t need to send the session as the server; the javascript just fishes it out of the cookie. But, that’s the exception and not the norm, so, yeah. definitely need to hash something.

  7. Simon Willison | 2008/10/03 at 23:22 | Permalink

    One thing that’s worth considering is that it’s easier to accidentally leak a CSRF crumb than it is to accidentally leak a session. Crumbs are used in forms, which means that if you accidentally include a crumb in a GET form it could end up in a URL – which might then leak to someone’s referrer logs. If you are automatically adding crumbs to any forms on your site you might accidentally add the crumb to a form with an action pointing somewhere else, which is another way that the crumb could leak.

    OK, so neither of the above are hugely likely, but just in case it’s nice to know that the crumb you are leaking is securely derived from your session ID rather than being identical to it.