Aaron Saray

open source programmer,
web developer

entrepreneur, author
and musician

My Blog

contains PHP, Web and business/entrepreneurial related content. Please join in the conversation!

Easiest Form Token class to prevent CSRF

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 validify a form submission.

So, let’s check out the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class formtoken
{
    const FIELDNAME = 'tok';
    const DO_NOT_CLEAR = FALSE;

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

    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

1
2
3
4
5
<form action="process.php" method="post">
<label>What is your name? <input name="name" /></label>
<input type="submit" />
<?php echo formtoken::getField() ?>
</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

1
2
3
4
5
6
if (formtoken::validateToken($_POST)) {
 /** do other stuff **/
}
else {
 die('The form is not valid or has expired.');
}

This entry was posted in PHP, security and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>