funkyres Posted February 16, 2009 Share Posted February 16, 2009 Howdy - as I'm getting closer to having my web app finished, I realized I had nothing for CSRF prevention. My admittedly limited understanding of CSRF is that the attacker forges a form submit and tricks a legitimate user into running it. Thus if the user has a valid session ID in their browser cookie, the server gets the post, reads the cookie, and trusts that the post is legitimate. So - this is what I'm doing to try and mitigate it. I modified my php sessions class (database driven) to add a CHAR(32) field. When a session is created, md5(microtime() . rand()); is inserted into that field. Forms then have that value as a hidden input, and when a form is submitted, the input value is checked against what it is the database for the users session ID. If it doesn't match, a redirect header is sent and the script die(); If a cracker is targeting a specific user, he may be able to intercept the key that is specific to the users session by snooping the traffic (user login/prefs is over ssl but nothing else is) but since I only use session cookies - the cracker would have to sniff the key during the users existing session or he won't be able to forge a form that would validate. If a cracker is not targeting a specific user, it's not an issue. I went ahead and tried it myself - and it seems to work. I don't want to rely on any of the referrer globals because those can be forged and some users for privacy reasons disable them. Anyway, does my scheme work or are there other measures I should take? Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 16, 2009 Author Share Posted February 16, 2009 I suppose an even safer way to do it would be to generate the key when the form is created and put in a database with the session id, and check the key + session id pair on form submit - deleting the pair on submit and run some kind of garbage collection that deletes pairs from expired sessions. That would pretty much eliminate the cracker targeting a specific user and snooping the network to try and catch a valid key. Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 16, 2009 Author Share Posted February 16, 2009 The per form method is working extremely well. Here's the sql table - CREATE TABLE csrf ( id MEDIUMINT unsigned NOT NULL AUTO_INCREMENT, sid varchar(32) NOT NULL, mykey varchar(32) NOT NULL, PRIMARY KEY (id) ); Here are the two php functions - first sets the key, second checks the key that was passed on post: <?php function csrfkey($sid) { $random = md5(microtime() . $sid . rand()); $sql = "INSERT INTO csrf (sid,mykey) VALUES ('$sid','$random')"; mysql_query($sql); return($random); } function checkcsrf($sid,$mykey) { $sql = "SELECT id FROM csrf WHERE sid='$sid' AND mykey='$mykey'"; $result = mysql_query($sql); while ($somevar = mysql_fetch_object($result)) { $valid = $somevar->id; } if (! isset($valid)) { return false; } else { $sql = "DELETE FROM csrf WHERE id=$valid"; mysql_query($sql); return true; } } ?> I think that should do it, unless anyone more experienced than me has other advice. Quote Link to comment Share on other sites More sharing options...
Daniel0 Posted February 16, 2009 Share Posted February 16, 2009 Using the database is overkill. Just use sessions. A brief example is given here: http://www.phpfreaks.com/tutorial/php-security/page8 Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 16, 2009 Author Share Posted February 16, 2009 The problem with using sessions is that with this application, it is certainly conceivable that one might have numerous forms open at the same time. I will when using the site. So either I need to have the same key for the duration of a session or use the database so I can have multiple keys that expire as soon as a they are used in a submit. Quote Link to comment Share on other sites More sharing options...
Daniel0 Posted February 16, 2009 Share Posted February 16, 2009 You can always just use the SID as CSRF token instead of manually generating a new one each time. Alternately you could keep an array of CSRF tokens for that user as session data. Either way, your code above is rather inefficient. Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 17, 2009 Author Share Posted February 17, 2009 The session ID itself is too dangerous to ever send to the client since if intercepted it can be used to forge a cookie to allow login as the user. But an md5 of the session ID + salt would probably be safe. The reason I want it to change though with each form - even though I don't use persistent cookies, session cookies only, a session can be active for quite some time - I know sometimes I have my browser open on my desktop for weeks (I run Linux so rebooting is really only necessary with a kernel update, I use noscript so pages that do obscene things w/ js and flash that cause firefox to crash rarely impact me). Every time a user requests a page, the clock for when their session expires server side is reset, so it is conceivable that a session could last for quite some time. Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 17, 2009 Author Share Posted February 17, 2009 The session ID itself is too dangerous to ever send to the client Doh - it's sent over http (from client to server) every time the cookie is read anyway. Quote Link to comment Share on other sites More sharing options...
Daniel0 Posted February 17, 2009 Share Posted February 17, 2009 If you're concerned about interception then you need to use SSL. Quote Link to comment Share on other sites More sharing options...
funkyres Posted February 18, 2009 Author Share Posted February 18, 2009 You still don't want to use the sid as the key. Cracker just needs to write a malicious script that downloads form and sends it to him. Cracker e-mail script to thousands of users. Cracker then parses what comes back for session ID's - thus eliminating the need to sniff packets and increasing the potential victim base. That would would work, btw, even if the server was https. Quote Link to comment Share on other sites More sharing options...
Daniel0 Posted February 18, 2009 Share Posted February 18, 2009 I think you'll find that it is not possible. The same origin policy protects against this. It prevents scripts from accessing data on websites it does not originate from. The origin is determined by the domain name (e.g. www.phpfreaks.com), application layer protocol (e.g. HTTP), and TCP port (e.g. 80). This means that a piece of Javascript running on http://example1.com is not able to access e.g. DOM http://example2.com because it's two distinct domain names and thus not the same origin. If the browser contains a hole that lets an attacker circumvent the same origin policy then there is nothing, as server side developer, you can do to protect your user. If the user's machine has been compromised then there is nothing you can do about it either. Using the SID is perfectly safe, but if you wish to tighten security then you can regenerate the SID every X requests for instance. You can try it for yourself. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.