6

I have read many articles about CSRF protection (this is a good one) and various questions here on SO, but none of them seem to be informative enough to answer my question.

I am developing my own CMS and I want to secure my login and comment forms. I am going to allow anonymous users to comment on my website.

All of the forms on my website are secured using tokens. I already know about that approach, but the problem is that it needs an active session (that is, after the user logs in). The problem with the login and comment forms is that they are accessible to just about anyone and do not require you to log in - what would be the best protection against CSRF in this case?

On the link above, I read that it could be possible to create a "pre-session" when the user tries to log in and then proceed to the usual anti-CSRF methods (like assigning a token to the user's session), but I have no insight on how to achieve this.

The referrer header is a weak solution, so I guess I shouldn't bother. The Origin header, is, as far as I have tested, only supported in Google Chrome. What about custom headers? XMLHTTPRequest seems like a possibility, however, I have spent literally more than three hours on Google looking up some information about how should one implement such a security measure on their website. But even if I could use a custom header, doesn't it make it useless since the HTTP headers can be faked completely?

So, the question: how should I protect my login and comment forms against CSRF?

Edit: here's some additional information from the link that I provided above:

We recommend strict Referer validation to protect against login CSRF because login forms typically submit over HTTPS, where the Referer header is reliably present for legitimate requests. If a login request lacks a Referer header, the site should reject the request to defend against malicious suppression.

and

Secret validation tokens can defend against login CSRF, but developers often forget to implement the defense because, before login, there is no session to which to bind the CSRF token. To use secret validation tokens to protect against login CSRF, the site must first create a “presession,” implement token-based CSRF protection, and then transition to a real session after successful authentication.

I just cannot put an end to this argument after reading the above quotes. One of them mentions using the referrer header, but I'm not quite sure whether it really adds much to the security of the webapp.

Edit 2: What about using CAPTCHAs?

Onion
  • 1,564
  • 1
  • 20
  • 40
  • This may be a question for [Security SE](http://security.stackexchange.com/) otherwise [OWASP](https://www.owasp.org/) is a good resource for web security. – HamZa Mar 26 '13 at 08:52
  • But how come there are so many questions on the same topic here on SO? – Onion Mar 26 '13 at 08:53
  • To "completely" protect a web application from a certain attaque is too broad (in my opinion), otherwise I'll be glad to see a good answer covering this issue (and that's why I upvoted :)) – HamZa Mar 26 '13 at 08:56
  • 1
    I am fully aware that there is no complete protection, but shouldn't we strive for the best? :) – Onion Mar 26 '13 at 08:57
  • I totally agree with you :D – HamZa Mar 26 '13 at 09:03

4 Answers4

3

The CSRF problem relates to someone using logged in user credentials to submit something. This is highly problematic as a malicious site can do stuff as anyone who's just browsed into your site. If you're talking about forms that can be used as anonymous, without logging in, there is lot less CSRF risk as there is considerably less to gain from posting to the form from another site - as anyone can do it directly also with same permissions.

So I don't get why protecting against CSRF for non-logged-in forms is needed.

If you do want this, a pre-session token could be technically similar to real session, but just a more light-weight one. It wouldn't really contain anything else than a generated token.


EDIT: about using the $_SESSION provided by PHP for the pre-session token, that's PHPs standard session mechanism. If you want to use that, then yes, that's about it.

However you're not forced to do it that way, and I personally wouldn't do it like that as it consumes server memory for all visitors, and that's not really needed. For a more efficient mechanism, basically you need a) a cookie identifying the user and b) something stored on the server side telling that the cookie is valid (and if needed, who is it valid for, meaning the ip). For a more light-weighted approach you can just create a token, store it in a cookie, and generate something matching that token in the form as hidden field, and match those on submit (like explained by Devesh). The latter would prevent submit of forms from another site, the former would prevent even the case where a malicious site does a lookup on your site and tries to set any cookies to the end user, too. So three approaches that I can think of:

  • just prevent image requests from other sites - using POSTs prevents this
  • prevent form submits from another site - form hidden field matching a cookie prevents this
  • prevent form submits from another site that do pre-lookup on your site - this would need IP verification, something stored on the server side, like ip in the database matched to the cookie

EDIT2: On captchas, their main use case is to prevent automated (brute force) login attempts. They would fix the issue with CSRF requests on login forms, too, but are an overkill for that. For preventing brute force login attacks they might be needed in some cases, although something more user friendly might be in order to not degrade usability too much. Maybe something like KittenAuth :)

shmuli
  • 4,576
  • 3
  • 28
  • 58
eis
  • 45,245
  • 11
  • 129
  • 177
  • So a "pre-session" would be the same `$_SESSION[]` variable I use after the user is logged in? As I understand, this would require me to start a new session every time the login page is accessed and assign a token to it. And AFTER a successful login, I should destroy the existing session FIRST, and then start a new one ("full-session", in this case), right? – Onion Mar 26 '13 at 09:06
  • @user1020567 added to the answer – eis Mar 26 '13 at 09:16
  • Changing session is not to prevent CSRF but to prevent session hijacking. The idea is that a user might be compromised while browsing insecure pages. Then after he logs in, you change the session so even if his session was compromised, they can no longer use that session after login. – Hugo Delsing Mar 26 '13 at 09:17
  • What good would the last one do? if I do a pre lookup with my site to your site. Then your cookie and IP verification would all be based on my sites information and when I do the second POST, my servers IP would still match the IP from the pre-request – Hugo Delsing Mar 26 '13 at 09:21
  • I already utilise the hidden tokens in forms. But is there any difference between using the active session to store the token's value and using a cookie for the same purpose? – Onion Mar 26 '13 at 09:21
  • @HugoDelsing it would prevent the case where a malicious site (fully in control of their stuff) does an anonymous pre-lookup on my site and generates HTML based on that to someone who is surfing their site. In that case ips would not match. Even if a malicious site is fully in control of their code, they usually cannot do pre-lookups as the user who is just surfing their site. – eis Mar 26 '13 at 09:30
  • @user1020567 a session normally uses cookies, too - the difference is that active session on the server side by default requires memory on your server (if you haven't done a custom session handling that stores it elsewhere), so someone can exhaust your memory if you create sessions on the memory for all anonymous people – eis Mar 26 '13 at 09:32
  • The first approach with sessions and tokens would already solve that. No need for IP verification. As I have no way to access the users tokens either. I can only generate my own. And if you are talking about session hijacking, then IP verification is in order. But it has nothing to do with CSRF – Hugo Delsing Mar 26 '13 at 09:36
  • So would you recommend using a token bound to the session when the user is logged in and rolling a token as a cookie when the user is not logged in (on login forms, for example)? – Onion Mar 26 '13 at 09:39
  • @HugoDelsing well, my view is that it does not. Consider this: malicious site does an anonymous pre-lookup, getting a cookie. Malicious site writes HTML that would set a cookie on a random web surfer, and then does a post submit in an iframe, forcing that cookie on the request done by random web surfer. That way malicious site can forge a request done by surfer to be coming from the initial request, if IP verification is in place. – eis Mar 26 '13 at 10:32
  • @user1020567 that's what I would do if I were presented with a task to prevent CSRF on anonymous forms. – eis Mar 26 '13 at 10:33
  • Hmm. But in all seriousness, is it really so much more secure to prevent CSRF on login forms? I mean, there's no active session going on anyway and the worst the attacker could do (as far as I've read) is simply login automatically from another site. – Onion Mar 26 '13 at 10:44
  • @user1020567 hence "So I don't get why protecting against CSRF for non-logged-in forms is needed." in the beginning of my answer. I've told how I would do it, if were asked to do it. I don't see a very good use case for doing it at all. CSRF issues relate to pretty much logged in users only. – eis Mar 26 '13 at 12:52
  • A session does not necessarily mean the user is already authenticated. So instead of using terms like “session” and “pre-session”, you should better use something like “session of an un-/authenticated user”. Furthermore, CSRF does not require the victim to be authenticated, although such a user is much more valuable for an attacker. – Gumbo Mar 26 '13 at 13:25
  • @Gumbo I think this whole thread has been about unauthenticated users. And yes, a session does not necessarily mean the user is already authenticated, but in the use case described by OP this was the case. – eis Mar 26 '13 at 14:10
0

You cannot realy protect an anonymous form against CSRF. Simply because the other site can act as a regular user. I can just create a site that does a curl request to the anonymous form and store the cookies and tokens in variables. And then make a second request to post the form. The script isnt realy forging a request, but is just posting automatically.

The point of CSRF is to prevent a script/person to perform actions on behalf of another user. So that would be me trying to post as you. To prevent that the session/cookie with token approach is a good a solution. Because I have no way to get your session and token, unless your site is flawed in other areas. I would suggest to read the OWASP guidelines to get some idea on what you should be on the lookout for.

Another thing you should always do is make sure "actions" are always with POST request so I cannot simple put on image on your forum that links to 'http://www.yoursite.com/delete.php?id=10'. If you allow GET request and you open the page that contains this image, I would have forged a request. If you only allow POST it would have no result.

Hugo Delsing
  • 13,127
  • 4
  • 37
  • 64
  • POST helps somewhat but not that much - if there is a site where javascript injection is possible, a malicious user can just put a form there and submit it through js (in a frame if needed so it would not even show up). – eis Mar 26 '13 at 08:58
  • The only time I use a GET request on my site is when I want to display a particular post. I get the id value from the url, sanitize it and proceed to the database, but there are no forms in this case. Am I safe? – Onion Mar 26 '13 at 09:00
  • 1
    there is no harm in using `GET` to display (get) data. You should normally not use it to ALTER (post/put) data. – Hugo Delsing Mar 26 '13 at 09:02
  • Yeah, I remember reading about not using GET for state-changing actions (such as inserting data to the database). – Onion Mar 26 '13 at 09:02
  • CSRF is about a web application that “[does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.](http://cwe.mitre.org/data/definitions/352.html)” In particular, it doesn’t require the victim to be authenticated, although such a victim is much more valuable for an attacker. – Gumbo Mar 26 '13 at 13:30
  • So you want to verify that an anonymous user is in fact the intented anonymous user? – Hugo Delsing Mar 26 '13 at 13:40
0

I think you can tackle the CSRF kind of problem by combining the hidden field added to your form and at the same time add the same value in the cookies and attach with the user response. When user post back the form try to match the hidden field value and the cookie value coming from the request , if both are matching you are good to go...

Devesh
  • 4,132
  • 1
  • 13
  • 25
  • Isn't the cookie approach vulnerable? Could you please elaborate a little bit more about using cookies to store the token (I use only the active session on my webapp). – Onion Mar 26 '13 at 09:08
  • You can see here : http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/ Same approach is used by Microsoft for ASP.NET MVC . There is never 100% safe approach but we can maximise the safety. – Devesh Mar 26 '13 at 09:10
0

he CSRF problem relates to someone using logged in user credentials to submit something. This is highly problematic as a malicious site can do stuff as anyone who's just browsed into your site. If you're talking about forms that can be used as anonymous, without logging in, there is lot less CSRF risk as there is considerably less to gain from posting to the form from another site - as anyone can do it directly also with same permissions.

So I don't get why protecting against CSRF for non-logged-in forms is needed.

If you do want this, a pre-session token could be technically similar to real session, but just a more light-weight one. It wouldn't really contain anything else than a generated token.

Tan
  • 1