tags:

views:

235

answers:

1

Hi there,

I have readed about how to prevent CSRF-attacks in the last days. I am going to update the token in every pageload, save the token in the session and make a check when submitting a form.

But what if the user has, lets say 3 tabs open with my website, and I just store the last token in the session? This will overwrite the token with another token, and some post-action is going to fail.

Do I need to store all tokens in the session, or is there a better solution to get this working?

Best regards, Erik Persson

+3  A: 

Yes, with the stored-token approach you'd have to keep all generated tokens just in case they came back in at any point. A single stored-token fails not just for multiple browser tabs/windows but also for back/forward navigation. You generally want to manage the potential storage explosion by expiring old tokens (by age and/or number of tokens issued since).

Another approach that avoids token storage altogether is to issue a signed token generated using a server-side secret. Then when you get the token back you can check the signature and if it matches you know you signed it. For example:

// Only the server knows this string. Make it up randomly and keep it in deployment-specific
// settings, in an include file safely outside the webroot
//
$secret= 'qw9pr$wyq%^ynrui2cni3';

...

// Issue a signed token
//
$token= dechex(mt_rand());
$hash= sha1($secret.'-'.$token);
$signed= $token.'-'.$hash;

<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>">

...

// Check a token was signed by us, on the way back in
//
$isok= FALSE;
$parts= explode($_POST['formkey'], '-');
if (count($parts)==2) {
    list($token, $hash)= $parts;
    if ($hash==sha1($secret.'-'.$token))
        $isok= TRUE;
}

With this, if you get a token with a matching signature you know you generated it. That's not much help in itself, but then you can put extra things in the token other than the randomness, for example user id:

$token= dechex($user->id).'.'.dechex(mt_rand())

...

    $userid= hexdec(explode($token, '.')[0]);
    if ($userid==$user->id && $hash==sha1($secret.'-'.$token)
        $isok= TRUE

Now each form submission has to be authorised by the same user who picked up the form, which pretty much defeats CSRF.

Another thing it's a good idea to put in a token is an expiry time, so that a momentary client compromise or MitM attack doesn't leak a token that'll work for that user forever.

bobince
But how to make it work in all windows?
Erik Persson
Er, yeah, this will work globally. Multiple tabs and multiple windows will be fine.
bobince
@bobince No, the session will be overwritten?
Erik Persson
If we're talking about the signed-token method above, nothing is written to the session. That's the point: by passing out signed messages in hidden fields you can know they came from you and trust them when they come back to you as a submission in the future, without having to remember any state. You don't need a session at all.
bobince
I wonder how it is possible to "un-hash" the expiry time in the token to check if the token has been expired.
Alexander Groß
You don't hash the time, you include it directly in the token passed to the client side. An attacker can see what the timestamp is, but they can't change it as that would invalidate the hash the server used to sign the token.
bobince