Easiest Form Token class to prevent CSRF

Oct 12, 2010 php security
This post is more than 18 months old. Since technology changes too rapidly, this content may be out of date (but that's not always the case). Please remember to verify any technical or programming information with the current release.

So, if you’re not familiar with CSRF, check out this blog post about AJAX Security. Some of the steps talking about Cross Site Request Forgeries will help you understand the problem.

I’ve been using a very simple system with my sites. This isn’t meant to be the end-all be-all of super secure tokenizing - but it’s good enough to stop the most common CSRF’s. Some other peer reviews of this code suggested that I wasn’t doing enough to stop collisions and generate enough uniqueness in the tokens. I understand the concern about this - but I am not encrypting CC or SSN here - we’re just generating tokens to somewhat validate a form submission.

So, let’s check out the class:

class formtoken
{
  const FIELDNAME = 'tok';
  const DO_NOT_CLEAR = FALSE;

  public static function getField()
  {
    $token = self::_generateToken();
    return '<input type="hidden" name="' 
           . self::FIELDNAME 
           . '" value="{$token}"></input>';
  }

  public static function validateToken($request, $clear = true)
  {
    $valid = false;
    $posted = isset($request[self::FIELDNAME]) ? $request[self::FIELDNAME] : '';

    if (!empty($posted)) {
      if (isset($_SESSION['formtoken'][$posted])) {
        if ($_SESSION['formtoken'][$posted] >= time() - 7200) {
          $valid = true;
        }
        if ($clear) unset($_SESSION['formtoken'][$posted]);
      }
    }

    return $valid;
  }

  protected static function _generateToken()
  {
    $time = time();
    $token = sha1(mt_rand(0, 1000000));
    $_SESSION['formtoken'][$token] = $time;
    return $token;
  }
}

In the class, two constants are defined. The first will be the field name that is used to generate the token form hidden input. The second is a constant for when I don’t want to clear the session token. (This is useful in cases where the first AJAX request has been validated to be new, so we can trust all future ones with that token.)

The next method, getField(), generates the token. It calls a protected method to get the actual content of the token. Then, a hidden input, with the class constant used to name the field, is created and returned.

I want to jump to the _generateToken() method now. A token is generated by taking a random number and then SHA1 hashing it. This is attached to the session as the formtoken array with the key of that token. The current time is also added. This will be necessary to validate older tokens - (so no one stores one from say 10 days ago). It is stored to the session and then returned to the caller.

Finally, the validateToken() method is called. It accepts the request and a variable detailing whether to clear the session of this token. The default action is yes. (See, you could tell it not to if you called it with something like this: formtoken::validateToken($_GET, formtoken::DO_NOT_CLEAR); )

The request is checked to make sure it has the token. Then, it verifies it exists in the session and that its not older than 7200 seconds. Finally, if we can clear the token, we do. The result of this is returned to the caller.

Let’s see how we might implement this.

formWeWantToTokenize.php
<form action="process.php" method="post">
<label>What is your name? <input name="name"></input></label>
<input type="submit"></input>
</form>

The only PHP call in this form is to formtoken::getField() which will return the hidden input with the generated token value.

Now, the processor must check that this is good to go:

process.php
<?php
if (formtoken::validateToken($_POST)) {
  /** do other stuff **/
}
else {
  die('The form is not valid or has expired.');
}
Go to All Posts