views:

268

answers:

2

Cross Site Request Forgery (CSRF) is typically prevent with one of the following methods:

  • Check referer - RESTful but unreliable
  • insert token into form and store the token in the server session - not really RESTful
  • cryptic one time URIs - not RESTful for the same reason as tokens
  • send password manually for this request (not the cached password used with HTTP auth) - RESTful but not convenient

My idea is to use a user secret, a cryptic but static form id and JavaScript to generate tokens.

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe fetched by the JavaScript from the authenticated user.
  2. Response: OK 89070135420357234586534346 This secret is conceptionally static, but can be changed every day/hour ... to improve security. This is the only confidential thing.
  3. Read the cryptic (but static for all users!) form id with JavaScript, process it together with the user secret: generateToken(7099879082361234103, 89070135420357234586534346)
  4. Send the form along with the generated token to the server.
  5. Since the server knows the user secret and the form id, it is possible to run the same generateToken function as the client did before sending and compare both results. Only when both values are equal the action will be authorized.

Is something wrong with this approach, despite the fact that it doesn't work without JavaScript?

+1  A: 

The static form ID provides no protection at all; an attacker can fetch it himself. Remember, the attacker is not constrained to using JavaScript on the client; he can fetch the static form ID server-side.

I'm not sure I entirely understand the proposed defense; where does the GET /usersecret/john_doe come from? Is that part of the page JavaScript? Is that the literal proposed URL? If so, I'm assuming that username is not a secret, which means that evil.ru can recover user secrets if a browser or plugin bug allows cross-domain GET requests. Why not store the user secret in a cookie upon authentication rather than let anyone who can do cross-domain GETs retrieve it?

I would read "Robust Defenses for Cross-Site Forgery" (http://crypto.stanford.edu/websec/csrf/csrf.pdf) really carefully before I implemented my own authentication system that I wanted to be resistant to CSRF. In fact, I would reconsider implementing my own authentication system at all.

Scott Wolchok
The form ID is something like a public key. You're right, `GET /usersecret/john_doe` is part of the JavaScript. The username itself is not the secret, but the ID fetched with this request by an authenticated(!) user.Thank you for the link.
deamon
A: 

You definitely need some state on the server to authenticate/authorize. It need not be the http session though, you could store it in a distributed cache (like memcached) or a database.

If you use cookies for authentication, the easiest solution is to double-submit the cookie value. Before you submit the form, read the session id from the cookie, store it in a hidden field and then submit it. On the server side, confirm that the value in the request is the same as the session id (that you got from the cookie). Evil script from another domain will not be able to read the session id from the cookie, thus preventing CSRF.

This scheme uses a single identifier across the session. If you want more protection, generate a unique id per-session per-form.

Also, DO NOT generate tokens in JS. Anybody can copy the code and run it from a different domain to attack your site.

sri
Sessions are not required for authentication as demonstrated by HTTP authentication. The JavaScript code to generate the token is not secret - only the user secret has to be secret.
deamon