Search code examples
phpformscodeignitercsrf

CSRF token not validated from times to times


I use Codeigniter/PHP. I use CSRF tokens (not the CI native version as I have my own form implementation) and from time to time the token is not validated.

The CSRF token is created once per session:

function create_csrf_token() //If needed, creates a session variable; returns a hash to use for CSRF protection
{
    $CI =& get_instance();
    if($CI->session->userdata('csrfToken')) //the token already exists: use its hash
    {
        $csrfHash = $CI->session->userdata('csrfToken');
    }
    else //no token yet: create session variable + set its hash
    {
        $csrfHash = base64_encode(hash('sha256', uniqid(serialize($_SERVER), true), true));
        $CI->session->set_userdata(array('csrfToken' => $csrfHash));            
    }
    return $csrfHash;
}

It is passed to the form without issues in the csrfToken hidden input field, with htmlspecialchars applied to it (using urlencode makes no difference):

echo '<input type="hidden" name="'.$this->name.'" value="'.htmlspecialchars($this->value).'">';

This field has a validation rule verify_csrf:

public function verify_csrf($token)
{
    $CI =& get_instance();
    if($CI->session->userdata('csrfToken') && $CI->session->userdata('csrfToken') == $token) return true;
    else
    {
        $this->set_message('verify_csrf', 'Invalid token');
        return false;
    }
}

This is where things get weird. Sometimes $token is not correct and looks like corrupted data. Here are a couple of examples:

Error:

Value in $CI->session->userdata('csrfToken'): 6cT3O0KTOk7cVlear71lU7KKFlGONt4rS2HjNoSVFRM= (correct)

Value in $token: 6cT O0KTOk7cVlear71lU7KKFlG (4th character changed and missing end of string)

No error:

Value in $CI->session->userdata('csrfToken'): AiAgGqqxTxuCxN7h5HHRtcJjmHJVMRksBYbq6Dx4Kv4=

Value in $token: AiAgGqqxTxuCxN7h5HHRtcJjmHJVMRksBYbq6Dx4Kv4=

Any idea? I have checked and rechecked, the CRSF token is correctly set everywhere except in $token in my validation callback. And it only happens for certain tokens...

EDIT: so it seems that the base64 encoding was causing the issue (why, I don't know). I have replaced

$csrfHash = base64_encode(hash('sha256', uniqid(serialize($_SERVER), true), true));

by

$csrfHash = random_string('sha1');

Solution

  • This is just a wild guess, but could it be the combination of the Base64 encoding and submitting the form via HTTP POST, like described here:

    POST Base64 encoded data in PHP

    The solution could then be to urlencode() the token before posting?

    EDIT: The solution turns out to be dropping the base64 encoding of the token in favor of a plain sha256-hash as a token. See comments below.