Question

To protect against CSRF you should put a nonce in a hidden field in the form, and in a cookie or in the session variable. But what if the user opens several pages in different tabs? In this case each tab would have a form with a unique nonce, but there would be only one nonce stored in the session variable or cookie. Or if you try to store all the nonces in the cookie/session variable, how would you identify which one belongs to which form?

Was it helpful?

Solution

You can store the same nonce in each of the forms. The easiest way to do it is to tie the nonce to the session ID, so that those forms only work in that session.

You will want to make it hard for attackers to snarf session IDs and create their own nonces. So, one way to go about it is to use HMAC-SHA256 (or the like) to hash the session ID, using a key that you do not expose to the public.

(Obviously if the attacker can get the actual session ID itself, they can already do session hijacking. So that's not what I'm talking about, but rather the ability for an attacker to craft a script (that runs on the victim's computer) that can somehow grab the session ID and use that to dynamically generate a URL with the nonce pre-filled.)


ETA: Whether the above approach is enough on its own depends on how long you expect your typical sessions to last. If users usually use long-lasting sessions spanning longer than a few hours, you'll need to use something more sophisticated.

One approach is to create a new nonce for each form, that contains the timestamp, as well as hash(timestamp . sessionid) (where hash is some variant of HMAC as described above, to prevent forgery, and . is string concatenation). You then verify the nonce by:

  1. checking the timestamp to ensure that the nonce is fresh enough (this is up to your policy, but a few hours is typical)
  2. then, calculating the hash based on the timestamp and session ID, and comparing against the nonce, to verify that the nonce is authentic

If the nonce check fails, you'll want to display a new form, pre-populated with the user's submission (so that if they took a whole day to write their post, they won't lose all their hard work), as well as a fresh nonce. Then the user can resubmit straight away successfully.

OTHER TIPS

What you're describing is not a nonce anymore (nonce = number used once), it's just a session identifier. The whole point of a nonce is that it is only valid for a single form submission, therefore offers greater security against hijacking than just a session ID, but at the cost of not being able to have multiple tabs operating in parallel on the site.

Nonces are overkill for many purposes. If you use them, you should only set and require them on forms that make critical changes to the system, and educate users that they cannot expect to use more than one such form in parallel. Pages which do not set a nonce should take care not to clear any previously stored nonce from the session, so that users can still use non-nonced pages in parallel with a nonced form.

Some people do generate a token for each form, and that is a very secure approach. However, this can break your app and piss off users. To prevent all XSRF against your site you just need unique 1 token variable per session and then the attacker will not be able to forge any request unless he can find this 1 token. The minor issue with this approach is that the attacker could brute force this token as long as the victim is visiting a website the attacker controls. HOWEVER if the token is pretty large like 32 bytes or so, then it would take many years to brute force, and the http session should expire long before then.

Long time back this post was written. I've implemented a csrf blocker that I'm almost certain protects well. It does function with multiple open windows, but I'm still assessing the kind of protection it offers. It uses a DB approach, ie storing instead of session to a table. NOTE: I use MD5 in this case as an easy anti-sqli mechanism

Pseudo Code:

FORM:

token = randomstring #to be used in form hidden input
db->insert into csrf (token, user_id) values (md5(token),md5(cookie(user_id))

-- the token is then kept in the db till it's accessed from the action script, below:

ACTION SCRIPT:

if md5(post(token)) belongs to md5(cookie(user_id)) 
    #discard the token
    db -> delete from csrf where token=md5(post(token)) and user_id=md5(cookie(user_id)) 
    do the rest of the stuff
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top