PHP CSRF Protection.

In this tutorial, we are going to show you how to guard against CSRF in PHP.

Cross-site Request Forgery (CSRF) is a type of attack that involves tricking the user into performing an action that they didn’t intend on carrying out.

This could be something as simple as directing a user to a logout URL. It could also be something much more serious.

For example, the attack might “trick” a user into deleting an important resource.

Example CSRF Attack.

Here is an example of a CSRF attack.

<!--Image HTML with src attribute set to a delete URL-->
<img src='delete.php?id=2324' />

If an attacker manages to trick a user into viewing the image above, then the user’s browser might delete an important record.

This is because the browser will attempt to load the URL in the image src.

As a result, it will send an HTTP request to delete.php.

CSRF prevention in PHP.

To prevent these kinds of attacks, we can give the user a randomly generated CSRF token when they log into our website.

We can then add this CSRF token to our HTML forms and query string parameters.

Creating a secure token with PHP’s openssl_random_pseudo_bytes function is actually pretty simple:

//After the user's login is successful.

//Generate a secure token using openssl_random_pseudo_bytes.
$myToken = bin2hex(openssl_random_pseudo_bytes(24));

//Store the token as a session variable.
$_SESSION['token'] = $myToken;

We can then add that token to our HTML forms (with a hidden field) like so:

<form action="process.php" method="post">
    <!--Hidden field containing our session token-->
    <input type="hidden" name="token" value="<?= $_SESSION['token']; ?>">
    <input type="text" name="email" placeholder="Your email address..."><br>
    <input type="submit" name="submit_form"> 
</form>

Or we can include the token as a query string parameter on important links:

<!--Protecting the logout URL against CSRF-->
<a href="logout.php?token=<?= $_SESSION['token']; ?>">Logout</a>

When the user attempts to carry out an important action, we can validate the CSRF token by comparing the submitted token against the token in the user’s session variable:

//Always make sure that you start your sessions.
session_start();

//For backward compatibility with the hash_equals function.
//This function was released in PHP 5.6.0.
//It allows us to perform a timing attack safe string comparison.
if(!function_exists('hash_equals')) {
  function hash_equals($str1, $str2) {
    if(strlen($str1) != strlen($str2)) {
      return false;
    } else {
      $res = $str1 ^ $str2;
      $ret = 0;
      for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]);
      return !$ret;
    }
  }
}

//Make sure that the token POST variable exists.
if(!isset($_POST['token'])){
    throw new Exception('No token found!');
}

//It exists, so compare the token we received against the 
//token that we have stored as a session variable.
if(hash_equals($_POST['token'], $_SESSION['token']) === false){
    throw new Exception('Token mismatch!');
}

//Token is OK - process the form and carry out the action.

As you can see, protecting against CSRF in PHP isn’t that difficult.

It just takes a little bit of added code.