Introduction
CSRF in a nutshell is an attack where the attacker gets a victim to click a link that submits a form to another web site. If the victim has authentication credentials at that other web site, it is possible that actions the user does not want to take place will take place on the server where the form is submitted.
The standard and very effective way of protecting users and your server against this kind of attack is to include some kind of a token as a hidden input in your forms. The token needs to be extremely difficult for an attacker to guess. If the proper token is not submitted with the form, the form action script does not process the request.
I have been accused of going a little overboard with my method of CSRF protection since I involve a database. So before presenting my method, I will discuss the simpler methods and why I do not personally like to use them. You may decide my concerns are not relevant to your scenario, or you may decide they are.
CSRF Prevention Methods
On the form processing side, all the post token based methods pretty much work the same, generally some variation of this:
$valid_post = false; if (isset($_POST['ptoken'])) { $valid_post = check_token($_POST['ptoken']); }
where check_token() is a function that checks to see if the post token validates.
The form action script then only does the requested operation if the $valid_post Boolean is set to true. Otherwise it either spits out an error, redirects to the home page or logout page, etc.
How the token is generated is where the different methods exist.
Session ID as Post Token
Some web developers will use the Session ID for a post token. For example:
<input type="hidden" name="ptoken" value="<?php echo(session_id());?>" />
I think it is a very bad idea for a users session ID to ever be incorporated in a web page since it makes it easier for a session to be hijacked. The argument is that the session ID is in already in a cookie, so unless you are using SSL, if an attacker wants the session ID the cookie can be sniffed just as easily as the web page.
However, some security technologies (such as the Suhosin php module) will transparently encrypt a cookie before sending it to the browser, making it much more difficult for the attacker to get a valid session ID from a sniffed cookie. As soon as you put that session ID unencrypted into your form, you have essentially neutered the session hijacking protection that those technologies make available to you.
Salted Session ID as Post Token
Another common technique is to use an md5sum of the session ID plus a salt. For example:
$ptoken = md5(session_id() . $salt); <input type="hidden" name="ptoken" value=" echo($ptoken);" />
While this method is much better than the first since it does not reveal the users session ID, some users may keep their session alive for a very long time. This is especially a problem with web applications that set the session ID cookie as a persistent cookie and/or do not force regeneration of the session ID with frequency.
If you do force regeneration of session ID during an active session, you run the risk of changing the users session after the user has started filling out a form but before the user actually submits the form. Users frequently surf around a site in another tab while they have form in the first tab, especially if they are a little confused by the form, so forcing a regeneration can cause a post failure to a legitimate user.
While the risk of this method to your users is much lower than the previous method, using a post token that will always be valid for the session could allow the attacker to attack a specific user and sniff the post token that corresponds to that users session allowing it to be used in a crafted CSRF attack. With the prevalence of open wireless networks, I believe this is something to be concerned about.
Dynamic Post Token Stored as Session Variable
A better method is to create a post token based upon the time and store it as a session variable that you can check against a submitted value. This is the best method that does not involve a database dedicated to CSRF protection. For example:
$ptoken = md5(session_id() . rand() . microtime()); $_SESSION['ptoken']=$ptoken; <input type="hidden" name="ptoken" value=" echo($ptoken);" />
This has the advantage of being somewhat dynamic. Every time the user visits a page that has a form, the value of the token is changed. When submitting the form, the server can check the submitted token against the session variable.
Unfortunately the dynamic nature causes another issue. If the user wanders around your site while filling out the form and stumbles upon another form, the session variable may be changed. Then when the user submits the first form, the token submitted will not match what is in the users session data.
Dynamic Post Token in Database
Sticking a dynamic post token in a database solves all these problems. If the user has opened multiple forms before submitting one, they can have multiple post tokens in the database without any of them being invalidated. As soon as a token is used, it gets deleted from the database. The token can also be specific to the action script where it can be used.
$csrf = new csrf($mdb2); $csrf->action = "e-mail reset"; $csrf->life = 20; $ptoken = $csrf->csrfkey(); <input type="hidden" name="ptoken" value=" echo($ptoken);" />
That is the method I use and the class described here.
Origin Header
Browsers may soon incorporate some built in CSRF protection, through an origin header. The proposal can be viewed here: Mozilla Security/Origin Wiki.