PHP CSRF Protection.

This is a short tutorial on how to guard against CSRF in PHP. Cross-site Request Forgery (CSRF) is a type of attack whereby the user is tricked into performing an action that they didn’t intend on carrying out. This could be as simple as directing the user to a logout URL or something as serious tricking them into deleting a resource.

Example CSRF Attack.

An example of a CSRF attack:

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

If an attacker manages to trick a user into viewing the above “image”, then the resulting HTTP request from the user’s browser could result in the deletion of an important resource. The browser will attempt to load the URL; resulting in a HTTP request being created by the user.

To guard against these kind of attacks, we can give the user a randomly generated CSRF token as soon as he or she logs in. We can then add this CSRF token to our HTML forms and to our query string parameters.

Generating a secure token with PHP’s openssl_random_pseudo_bytes function is actually pretty straight-forward:

<?php

//After the user's login has deemed to be 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 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:

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

Then, when the user is attempting to carry out an important action, we can validate the CSRF token by comparing the received token against the token that has been stored in the user’s session:

<?php

//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 difficult. It just takes a little bit of added code!