New CSRF token per request or NOT?

John picture John · May 5, 2012 · Viewed 24.5k times · Source

So I am reading around and was really confused about having a CSRF token, whetever I should generate a new token per each request, or just per hour or something?

$data['token'] = md5(uniqid(rand(), true));
$_SESSION['token'] = $data['token'];

But let's say it's better to generate a token each hour, then I would need two sessions: token, expiration,

And how will I proceed it to the form? Just put echo $_SESSION['token'] on the hidden value form and then compare on submit?

Answer

Laurence picture Laurence · May 6, 2012

If you do it per form request - then you basically remove the ability for CSRF attacks to occur & you can solve another common issue: multiple form submission

In simple terms - your application will only accept form input if the user ASKED for the form prior to the submission.

Normal scenario: User A goes to your website, and asks for Form A, is given Form A plus a unique code for Form A. When the user submits Form A, he/she must include the unique code which was only for Form A.

CSRF Attack scenario: User A goes to your website, and asks for Form A. Meanwhile they visit another "bad" site, which attempts a CSRF attack on them, getting them to submit for a fake Form B.

But your website knows that User A never asked for Form B - and therefore even though they have the unique code for Form A, Form B will be rejected, because they dont have a Form B unique Code, only a Form A code. Your user is safe, and you can sleep easy at night.

But if you do it as a generic token, lasting for an hour (like you posted above) - then the attack above might work, in which case you've not achieved much with your CSRF protection. This is because the application does not know that form B was never asked for in the first place. It is a generic token. The WHOLE POINT of CSRF prevention is to make each form token unique to that form

Edit: because you asked for more information: 1 - you dont have to do it per form request, you can do it per hour/session etc. The point is a secret value that is given to the user, and resubmiited on the return. This value is not known by another website, and thus cannot submit a false form.

So you either generate the token per request, or per session:

// Before rendering the page:
$data['my_token'] = md5(uniqid(rand(), true));
$_SESSION['my_token'] = $data['my_token'];

// During page rendering:
<input type="hidden" name="my_token" id="my_token" value="<? php echo $_SESSION['my_token']?>" />

// After they click submit, when checking form:
if ($_POST['my_token'] === $_SESSION['my_token'])
{
        // was ok
}
else
{
          // was bad!!!
}

and because it is "per form" - you wont get double form submissions - because you can wipe the token after the first form submission!