Cross-site request forgery [CSRF] is a type of attack where a user is tricked/forced into performing an unwanted action on a friendly website that they are authenticated with. For example, if a user is logged into their bank and then visits a malicious site, it is possible that the malicious site can use the user’s session to make requests to the bank server. Essentially, the malicious script inherits the user’s credentials and authorization to the bank’s site and can act on the user’s behalf. Since every request that the user makes to the bank’s server includes the session and cookie data, a request from the user’s browser that is initiated from another site to the bank will include this information as well. Since a CSRF attack uses the user’s browser and session, the bank server cannot identify that the request is malicious.
A simplified example of a CSRF attack is a user being logged into their bank and then visiting a page that has this image element:
<img src="http://www.mybank.com?transfer=$1,000&to=maliciousaccount" />
While this is a very simple example, you get the idea of how a user could tricked into making this request to their bank, without even knowing they have done it, even with Javascript disabled. Also, since the request is made by the user’s browser, security measures such as https are ineffective.
To combat these attacks, the Open Web Application Security Project suggests in their Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet to use the synchronizer token pattern. This method requires a unique random challenge token to be sent with each request that only the server can identify as being a valid request and can be sent to the server with a form using a hidden form field or included as a variable in a request. It is also critical that the token be used only in POST requests, so that it is not exposed, such as in the referrer section of an http request to a malicious site or when a user copies and pastes a url to share with a friend.
A common technique is to use a unique algorithm using the session id combined with the form/request that is being validated, meaning all tokens are form/request specific and expire when a user logs out. Using the session id alone would not be enough, since it can be discovered and used. One algorithm to generate a token could be concatenating the name of the form/request with the session id and running that through a hashing function like md5 or sha1 like this:
/** * generate CSRF token * * @author Joe Sexton <joe@webtipblog.com> * @param string $formName * @return string */ function generateToken( $formName ) { if ( !session_id() ) { session_start(); } $sessionId = session_id(); return sha1( $formName.$sessionId ); }
This can be taken one step further and add a secret key to make the token that much more difficult to duplicate:
/** * generate CSRF token * * @author Joe Sexton <joe@webtipblog.com> * @param string $formName * @return string */ function generateToken( $formName ) { $secretKey = 'gsfhs154aergz2#'; if ( !session_id() ) { session_start(); } $sessionId = session_id(); return sha1( $formName.$sessionId.$secretKey ); }
The next step is to add this token into the form using a hidden form field:
<input type="hidden" name="csrf_token" value="<?php echo generateToken('protectedForm'); ?>"/>
Now a method will be needed to validate the token that is received with the form, this is easy:
/** * check CSRF token * * @author Joe Sexton <joe@webtipblog.com> * @param string $token * @param string $formName * @return boolean */ function checkToken( $token, $formName ) { return $token === generateToken( $formName ); }
Now to validate the incoming form is valid, just check the token:
if ( !empty( $_POST['csrf_token'] ) ) { if( checkToken( $_POST['csrf_token'], 'protectedForm' ) ) { // valid form, continue } } // end if
While your site may or may not be a high CSRF target, CSRF prevention is very easy to implement and should be used in any application that handles a form or request. CSRF prevention must go beyond using just a static secret key or limiting form submission to POST requests, both of these solutions are still vulnerable to CSRF attacks. A unique identifier that cannot be predicted is critical to successful CSRF security. It is also important to safeguard all POSTed requests, not just form submissions.
Great code.
Can use filter input and/or sanitize for filtering $_POST?