Csrf token

#1

Hello, i often notice that members are asking for help with commerce apps in the beginners category. I find it strange to be creating shopping carts but offering no protection for the customers. I want to post a csrf token snippet so that members have something to strengthen form security. Keep in mind that this code is based upon my original token with changes suggested by Yasuo Ohgaki. My original token stored the hash in the session, whereas Yasuo stores the secret, then rehashes the key for matching. Yasuo uses hash_hkdf, whereas i was using hash_hmac. I’ve added strspn and join methods used by Yasuo. Otherwise, my token was nearly exact. I thank Yasuo for making me aware of rehashing the secret key and for showing me better validation methods.

My method creates a timestamp expiration outside of the function because i wish to show this time limit to users at my login screen (similar to certain banks.) I couldn’t think of an easier method of showing this info while passing it to the function, so i use three variables instead. If you have no need to display a time limit to a user, then you can just pass the expiration to the function in seconds (e.g., 1800 is 30 minutes, then set $expiration += time() in the createCSRFtoken function.), Meantime, i use a stronger hash (sha3-512, but this can be lessened if you wish to do so but nothing less than sha-256 is secure enough for a token.)


//page where token is set
$TokenExpiration = 180; $TokenExpiration += time(); $TokenBirth = $TokenExpiration - 180;
$_SESSION['CSRFtokenSecret'] = random_bytes(32);
$CSRFtoken = createCSRFtoken($_SESSION['CSRFtokenSecret'], $TokenExpiration);

//processing page after form submission
$validToken = validateCSRFtoken($_SESSION['CSRFtokenSecret'], $_POST['tokenStamp']);
unset($_SESSION['CSRFtokenSecret']); unset($_POST['tokenStamp']);
if ($validToken === true) {
    //process form
} else {
    header('Location: /');
  exit;
}


//functions to create and to validate a token
function createCSRFtoken($sessionSecret, $expiration, $info='') {
    $salt = bin2hex(random_bytes(32));
    $key = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $info."\0".$expiration, $salt));
    $token = join('-', [$salt, $key, $expiration]);
  return $token;
}
function validateCSRFtoken($sessionSecret, $postToken) {
    if (!is_string($sessionSecret) { return false; }
    if (strlen($sessionSecret) != 32) { return false; }
    if (!is_string($postToken)) { return false; }
    if  (strlen($postToken) != 204) { return false; }
    if ($postToken === '') { return false; }
    $getToken = explode('-', $postToken);
    if (count($getToken) !== 3) { return false; }
    list($salt, $key, $expiration) = $getToken;
    if (empty($salt) || empty($key) || empty($expiration)) { return false; }
    if (strlen($expiration) != strspn($expiration, '1234567890')) { return false; }
    $reHashKey = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0,  $info."\0".$expiration, $salt));
    if (hash_equals($key, $reHashKey) === false) { return false; }
    if ($expiration < time()) { return false; }
  return true;
}
1 Like
#2

update: i noticed an error in my code. i mistakenly tested the string length as !strlen() instead of strlen() !=

my apologies. i’ve updated this code.

#3

I’ve edited this token to add the correct parameters for info and salt as recommended by Yasuo. I failed to notice that i left this info out of the original post. My apologies.

so the correct method is as follows:

$key = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $info."\0".$expiration, $salt));

and this conforms to the method used by Yasuo. Final edit. Enjoy!

#4

Thank you, but how to integrate it for example with prestashop

#5

Just add the hidden input to your forms, like so:

<input type="hidden" name="tokenStamp" value="<?php echo $CSRFtoken; ?>" />

then add the following code to your form processing page after form submission:

$validToken = validateCSRFtoken($_SESSION['CSRFtokenSecret'], $_POST['tokenStamp']);
unset($_SESSION['CSRFtokenSecret']); unset($_POST['tokenStamp']);
if ($validToken === true) {
    //process form
} else {
    header('Location: /');
  exit;
}

very easy to add a token. Yasuo has helped me strengthen my original token.
Just remember to set the SESSION variable as illustrated in my post.

1 Like
#6

also, if you have no need to display the expiration to the user, then alter the process like so:

$CSRFtoken = createCSRFtoken($_SESSION['CSRFtokenSecret'], 1800);
//1800 seconds is 30 minutes. so pass seconds as expiration

function createCSRFtoken($sessionSecret, $expiration, $info='') {
    $expiration += time();
    $salt = bin2hex(random_bytes(32));
    $key = bin2hex(hash_hkdf('sha3-512', $sessionSecret, 0, $info."\0".$expiration, $salt));
    $token = join('-', [$salt, $key, $expiration]);
  return $token;
}

edit: sorry. i mistakenly listed 180 seconds (3 minutes) as 30 minutes. i am tired and i use 3 minutes for my login forms, thus 1800 is stuck in my head. i’ve changed the example to 1800s

1 Like