51

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?

John
  • 2,900
  • 8
  • 36
  • 65
  • 3
    Beware that generating a new token per request can cause problems if a user has your application open in two different browser windows at the same time. The tokens will get out of sync. – Michael Berkowski May 05 '12 at 22:12
  • @Michael That problem could be solved with the push technology , html 5 comunication standards . – Cata Cata May 05 '12 at 22:41
  • 2
    If it's not high-security, just take a hash e.g. of the user's ip and his password hash. That might never or only rarely change but it still serves its purpose since an attacker won't know the user's password (and if he does, he doesn't need to perform a CSRF attack on him) – ThiefMaster May 06 '12 at 09:40
  • @ThiefMaster: any reason to hash real data (like user's ip or password) and not generate a random one? – zerkms May 06 '12 at 09:55
  • Yes, this way you do not need to store the token. And it doesn't change often => more comfortable for users who use their back button and multiple tabs. – ThiefMaster May 06 '12 at 10:19
  • i've improved my answer for you – Laurence May 12 '12 at 11:19
  • John - do you need more info? Was my updated answer sufficient? – Laurence May 15 '12 at 04:43
  • Also see http://stackoverflow.com/questions/8655817/csrf-protection-do-we-have-to-generate-a-token-for-every-form and http://stackoverflow.com/questions/7433722/csrf-tokens-how-to-implement-properly and http://security.stackexchange.com/questions/22903/why-refresh-csrf-token-per-form-request – Lode Oct 22 '12 at 09:01

5 Answers5

42

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!

Laurence
  • 58,936
  • 21
  • 171
  • 212
  • 18
    "then the attack above might work" --- how? Elaborate please (this **incorrect** answer shouldn't be upvoted, because answerer has no idea what csrf-token is used for) – zerkms May 06 '12 at 09:48
  • 1
    because if you have generic CSRF tokens, then as long as the hacker has User A submit the form B, it will succeed, 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. – Laurence May 06 '12 at 09:50
  • 18
    how would attacker know csrf-token value? "The WHOLE POINT of CSRF prevention is to make each form token unique to that form" --- nope, you're wrong. – zerkms May 06 '12 at 09:52
  • the attacker doesnt need the CSRF token - thats the whole way CSRF works - it means the hacker has the USER click on a link, which submits a form on the hackers behalf BY the USER themselves! So they way to protect against CSRF attacks is to not let users submit forms they never asked for in the first place – Laurence May 06 '12 at 09:53
  • 1
    https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern – Laurence May 06 '12 at 09:53
  • 4
    Form should contain csrf-token, that is how csrf-token-based protection works. And attacker has no way to know it. I recommend you to read about it a little before you continue this discussion – zerkms May 06 '12 at 09:54
  • Zerkms - please read the OWAPS standards "By including a challenge token with each request, the developer has a strong control to verify that the user actually intended to submit the desired requests." - so perhaps you might want to read my link :) – Laurence May 06 '12 at 09:56
  • 1
    I see the phrase to include it into each request, but I don't see requirement for token to be **different** on each request. Do you see that on that page? So, where I'm incorrect? Please read the links carefully before you want to use them as a proof – zerkms May 06 '12 at 09:58
  • yes Zerkms - I do see it on that page - right here: "...To further enhance the security of this proposed design, consider randomizing the CSRF token parameter name and or value for each request" – Laurence May 06 '12 at 10:00
  • 1
    and how is it related with your answer? You state that constant token could lead to successfull attack. How so? How being an attacker would you know even constant token??? PS: it is not **requirement**, it is just an alternative. Static tokens aren't more vulnerable – zerkms May 06 '12 at 10:01
  • 8
    let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10926/discussion-between-laurencei-and-zerkms) – Laurence May 06 '12 at 10:01
  • So basically: $data['token'] = md5(uniqid(rand(), true)); $_SESSION['my_token'] = $data['my_token']; could be intiatied once? – John May 12 '12 at 11:30
  • Yes John - using that above will protect you from CSRF attacks. You can tweak the implementation to suit your needs - but the general structure is there – Laurence May 12 '12 at 13:40
  • p.s. I fixed a typo: $data['token'] should be $data['my_token'] – Laurence May 12 '12 at 14:05
  • we are facing issues with one request per post call, i am using angular5 and using HttpClientXsrfModule to read the cookie and send it as header, but if there are a bunch of post calls in parallel sometime the UI sends a different token in header and cookie due to concurrency. – Suresh Nagar Jun 12 '18 at 02:13
  • Definitely, afaik, this paragraph is incorrect: «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.» The attacker cannot get "the unique code for Form A". – Duarte Cunha Leão Mar 02 '21 at 14:39
24

Generally, it suffices to have one single token per user or per session. It is important that the token is bound to just one particular user/session and not globally used.

If you are afraid the token could get leaked to or get obtained by an attacking site (e. g. by XSS), you can limit the token’s validity to just a certain timeframe, a certain form/URL, or a certain amount of uses.

But limiting the validity has the drawback that it might result in false positives and thus restrictions in usability, e. g. a legitimate request might use a token that just have been invalidated as the token request is too long ago or the token has already been used too often.

So my recommendation is to just use one token per user/session. And if you want further security, use one token per form/URL per user/session so that if a token for one form/URL gets leaked the others are still safe.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • Please define "per user". What does that mean? What qualifies as a user? Are you referring to IP based user-tracking? – hakre May 06 '12 at 12:07
  • 1
    @hakre You would normally use a session to associate the token to the session. But there are user accounts, you can also associate the token to a user directly. That’s what I meant by “per user”. – Gumbo May 06 '12 at 12:29
  • And the session? Identified by cookie? Wouldn't that leave a hole in that context? I mean as long as the session identifier can be passed within the request, this would not work, right? Albeit the infos given are useful for guidance, just trying to circle round a bit. – hakre May 06 '12 at 12:30
  • 1
    @hakre No, the session is just used to associate the token to the request and both have to be equal to make the request authentic. – Gumbo May 06 '12 at 12:32
  • @Gumbo Isn't it also a good idea to regenerate the token after any successful form submit or after the user's privileges have changed? – Kid Diamond Aug 04 '14 at 09:01
  • 1
    Just wanted to add that I did CSRF tokens on a per request basis and am moving to per user/session. I ran into the issue of false positives, which are likely being caused by a concurrency issue by storing CSRF tokens in a map in the session. Going forward I plan to keep tight XSS security, via such as standards like CSP and keep CSRF simple. – Jake88 May 07 '20 at 15:22
3

The answer to your question is: it depends.

And you don't need to use session for timed tokens, you can just use the server-time and a secret key on the server.

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

No, you need a routine that is able to generate a token for a time-frame. Let's say you divide time per 30 minutes. The you create one token for the current 30 minutes in the form.

When then form is submitted and you verify the token against for now and against the previous 30 minute period. Therefore a token is valid for 30 minutes up to one hour.

$token = function($tick = 0) use($secret, $hash) {
    $segment = ((int) ($_SERVER['REQUEST_TIME'] / 1800)) + $tick;
    return $hash($secret . $segment);
};
Kemal Fadillah
  • 9,760
  • 3
  • 45
  • 63
hakre
  • 193,403
  • 52
  • 435
  • 836
  • @John On your needs naturally, if the `$_SESSION` based method works for you, go for it. I've used it for a hotlink protection for downloads once and it worked well. – hakre May 06 '12 at 09:20
  • @john: I added an exemplary function how to generate tokens that are valid for a specific period of time w/o using sessions. – hakre May 06 '12 at 09:31
  • 1
    CSRF token with 30 minutes TTL is as secured as 24 hours TTL token, and 1 minute ttl token. But the less TTL is - the more chances for user to catch the case when CSRF token became invalid. So I vote for 1 CSRF token per session, without invalidations/regenerations – zerkms May 06 '12 at 09:33
  • @zerkms - you're right and the 30 minutes is exemplary. Often sessions do expire within 60 minutes, so a session based CSRF token has something like that as well (albeit it behaves a bit differently as the timeout period extends with each interaction). However, after each hour, the session id should be regenerated as well to prevent sessions that can be kept open unlimited. – hakre May 06 '12 at 09:45
  • -1 This does not protect against CSRF attacks. An attacking site just needs to obtain a valid token from the server by itself and that’s it. – Gumbo May 06 '12 at 11:18
  • @Gumbo: As well as anything else won't prevent against IP spoofing or what? – hakre May 06 '12 at 12:06
  • @hakre No, CSRF has nothing to do with IP address spoofing. – Gumbo May 06 '12 at 12:30
  • Ugh. Could you clarify the function in your post? I can't get it work. I would really appreciate, If you show a real example, like the form. – John May 06 '12 at 14:48
  • Just call the function and insert the value (and fix the typo probably): `
    ...` - with the form submit compare against `$token()` and if non-matching `$token(-1)`.
    – hakre May 06 '12 at 14:52
  • @John You should not use these tokens. They do not protect against CSRF as they are not bound to a user or session but are globally valid. And attacking site would just need to fetch the current valid token and would then be able to forge valid requests. – Gumbo May 06 '12 at 18:44
  • @Gumbo: Not if you create the secret and store it in a cookie - better even part of it. – hakre May 06 '12 at 18:56
2

At owasp site it mentions that

Per-request tokens are more secure than per-session tokens as the time range for an attacker to exploit the stolen tokens is minimal. However, this may result in usability concerns.

And it concludes that

CSRF tokens should be:

  • Unique per user session.
  • Secret
  • Unpredictable (large random value generated by a secure method).
Alireza Fattahi
  • 42,517
  • 14
  • 123
  • 173
0

You can use both methods.

  1. A random token for each request. And to solve problem of not syncronised tokens, you can use server-sent events http://www.w3.org/TR/eventsource/ or websockets http://www.w3.org/TR/websockets/ comunication technology to update the tokens in real time for every page.

  2. You can use a token per user session (no need to do it per hour) that is easyer to implement. You wont have problem with syncronisation but if the token is not too strong it can be guessed. Of course if the token is very random it will be very hard for a malicious person to actually do a request forgery.

P.S. the first method is the most secure but is harder to implement and uses more resources.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Cata Cata
  • 166
  • 1
  • 9