Can you use just the user's session ID as an anti-CSRF token? Let's break down CSRF tokens and how to create them.

What is a CSRF attack?

CSRF attack is when another site forges a request against your site as if one of you logged in visitors is performing the actions (usually form submits). From Wikipedia:

A user who is authenticated by a cookie saved in the user's web browser could unknowingly send an HTTP request to a site that trusts the user and thereby causes an unwanted action.

Note that we won't be dealing with Login CSRF attacks in this post.

If you don't know what a CSRF attack is, go look it up because the rest of the article won't make sense to you.

Most CSRF attacks are mitigated 2 ways

First, make sure you're only mutating the server state via POST or PUT commands. GET commands are much easier to execute cross-site.

Secondly, I forget the second way. It's not important.

What's wrong with setting a new CSRF cookie?

Django sets a separate CSRF cookie that lasts the lifetime of the session. It does not use a new nonce per form. So, what's wrong with setting a new random value in a cookie along side the session cookie?

  • It's more data to transfer.
  • It's more cookie settings to manage. (expiry, path, domain)
  • It's fundamentally the same thing as a session ID.
  • When do you regenerate it? On login? See above.

What's wrong with attaching a server side value to the session?

Rails adds a value inside the session. This is fine too, it's largely the same as setting a new cookie because the value is exposed to the client when the form is rendered, so it's not secret.

  • It's fundamentally the same thing as a session ID.
  • It's just another opaque value to keep track of and debug.

What's wrong with using the session ID?

Oh no! Don't expose the session ID. It just feels wrong so it must be super wrong because we're talking about security and we must use superlatives.

According to 2 different sources, the only problem with using the session ID is that it exposes the session ID to HTML and all the caching, debugging, and copy & pasting problems that come with that.

http://www.adambarth.com/papers/2008/barth-jackson-mitchell-b.pdf

https://stackoverflow.com/questions/518313/csrf-validation-token-session-id-safe

Solution: One way hash the session ID

Just MD5() it. done.

What a dumb article, what about nonce values?

Don't use nonce values. I use multiple tabs and if you record a singleton nonce value everytime I load a form and then I can't go back to a different tab and submit that first form I will dislike your site.

Use predictable opaque values if you must use a separate value for each form.

$form->addCsrfToken( md5($user->getSessionId() . $form->getName());
//or
$form->addCsrfToken( md5($user->getSession() . $relativeUrlToThisPage());

That way, when you're validting the form you have all the information necessary to validate the token ahead of time.

$form->validateCsrfToken( $input->get('csrf-token'), md5($user->getSessionId() . ' $form->getName()));

This keeps the session ID hidden from the browser and HTML caching, while still giving a predictable, multi-tab friendly way to have a per-form CSRF token.

The purpose of a CSRF token is to ensure that the request came from your site - not from a cross site. It doesn't have to be special or fancy, it just has to exist and not be duplicatable by javascript or images hosted on another site. A user's session cookie is not accessible by another site (if you use httponly cookie attribute).

Remember, if an attacker has access to the session token then you're screwed far beyond a CSRF attack.

... wait, what's Login CSRF?

That's sort of a reverse attack where a malicious site logs someone in onto their own malicius account from another site and then tricks them into performing an action because the victim thinks their logged in with their own account. Think eBay, you might be logged in to eBay, a malicious site submits a login with their own credentials via your browser, when you go back your logged in to their account and you add your credit card or something. It's more social engineering, but you don't want to let your own users be tricked into that.

So, how do you protect your users from being tricked into giving away their credit card info? You need to restrict your login page with an HTTP Referrer check. Strict check it, but if the header is not present just present a second login screen or mark the session as not fully trust worthy (i.e. don't let them perform sensitive account operations without reverifying password)

Yeah, your sessions should have a trust metric as well. ... and you should re-ask for the password if it's been a while since they've entered the password and they're going to place an order or change billing info.

If someone logs in without an HTTP Referer header just do:

$session->setSketchy(true);

And then before sensitive account operations check:


class SensitiveController {
  function updateCreditCardInfo($request, $response) {
    if ($session->isSketchy()) {
      //save POST variables
      //redirect to password entry page
    }
    //...
}