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');