diff --git a/public/include/classes/csrftoken.class.php b/public/include/classes/csrftoken.class.php index 68f8e427..ca303c7b 100644 --- a/public/include/classes/csrftoken.class.php +++ b/public/include/classes/csrftoken.class.php @@ -5,35 +5,69 @@ if (!defined('SECURITY')) die('Hacking attempt'); class CSRFToken Extends Base { /** - * Gets a basic CSRF token for this user/type and time chunk - * @param string user User; for hash seed, if username isn't available use IP - * @param string type Type of token; for hash seed, should be unique per page/use - * @param string timing Which date() chars we add to the seed; default month day year hour minute ie same minute only - * @param string seedExtra Extra information to add to the seed - * @return string CSRF token + * Gets a basic csrf token + * @param string $user user or IP/host address + * @param string $type page name or other unique per-page identifier */ - public function getBasic($user, $type, $timing='mdyHi', $seedExtra='') { - $date = date('m/d/y/H/i/s'); - $data = explode('/', $date); - $month = $data[0]; $day = $data[1]; $year = $data[2]; - $hour = $data[3]; $minute = $data[4]; $second = $data[5]; - $salt1 = $this->salt; $salt2 = $this->salty; $seed = $salt1; - $lead = $this->config['csrf']['leadtime']; - $lead_sec = ($lead <= 11 && $lead >= 0) ? $lead : 3; - if ($minute == 59 && $second > (60-$lead_sec)) { - $minute = 0; - $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; - } - $seed.= (strpos($timing, 'm') !== false) ? $month : ''; - $seed.= (strpos($timing, 'd') !== false) ? $day : ''; - $seed.= (strpos($timing, 'y') !== false) ? $year : ''; - $seed.= (strpos($timing, 'H') !== false) ? $hour : ''; - $seed.= (strpos($timing, 'i') !== false) ? $minute : ''; - $seed.= (strpos($timing, 's') !== false) ? $second : ''; - $seed.= ($seedExtra !== '') ? $seedExtra.$salt2 : $salt2; + public function getBasic($user, $type) { + $date = date('m/d/y/H/i'); + $d = explode('/', $date); + $seed = $this->buildSeed($user.$type, $d[0], $d[1], $d[2], $d[3], $d[4]); return $this->getHash($seed); } + /** + * Returns +1 min and +1 hour rollovers hashes + * @param string $user user or IP/host address + * @param string $type page name or other unique per-page identifier + * @return array 1min and 1hour hashes + */ + public function checkAdditional($user, $type) { + $date = date('m/d/y/H/i'); + $d = explode('/', $date); + // minute may have rolled over + $seed1 = $this->buildSeed($user.$type, $d[0], $d[1], $d[2], $d[3], ($d[4]-1)); + // hour may have rolled over + $seed2 = $this->buildSeed($user.$type, $d[0], $d[1], $d[2], ($d[3]-1), 59); + return array($this->getHash($seed1), $this->getHash($seed2)); + } + + /** + * Builds a seed with the given data + * @param string $data + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @return string seed + */ + private function buildSeed($data, $year, $month, $day, $hour, $minute) { + return $this->salty.$year.$month.$day.$data.$hour.$minute.$this->salt; + } + + /** + * Checks if the token is correct as is, if not checks for rollovers with checkAdditional() + * @param string $user user or IP/host address + * @param string $type page name or other unique per-page identifier + * @param string $token token to check against + * @return boolean + */ + public function checkBasic($user, $type, $token) { + if (empty($token)) return false; + $token_now = $this->getBasic($user, $type); + if ($token_now !== $token) { + $tokens_check = $this->checkAdditional($user, $type); + $match = 0; + foreach ($tokens_check as $checkit) { + if ($checkit == $token) $match = 1; + } + return ($match) ? true : false; + } else { + return true; + } + } + /** * Convenience method to get a token expired message with a token type, and ? image with description * @param string $tokentype if you want a specific tokentype, set it here diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index f279963b..8742bcb3 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -135,17 +135,13 @@ $config['twofactor']['options']['changepw'] = true; * * Options: * enabled = Whether or not we will generate/check for valid CSRF tokens - * leadtime = Length of time in seconds to give as leeway between minute switches - * * Don't change this unless you know why you're changing it * disabled_forms = Which forms you want to disable csrf protection on, if enabled * * Valid options : login, contact, accountedit, workers, notifications, invite, register, passreset, unlockaccount * Default: * enabled = true - * leadtime = 3 * disabled_forms = array(); */ $config['csrf']['enabled'] = true; -$config['csrf']['leadtime'] = 3; $config['csrf']['disabled_forms'] = array(); /** diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 5fbc8d0a..133687e6 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -16,7 +16,7 @@ $updating = (@$_POST['do']) ? 1 : 0; // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('accountedit', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editaccount') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'editaccount', @$_POST['ctoken'])) ? 1 : 0; } if ($user->isAuthenticated()) { diff --git a/public/include/pages/account/invitations.inc.php b/public/include/pages/account/invitations.inc.php index e3328f01..10a85149 100644 --- a/public/include/pages/account/invitations.inc.php +++ b/public/include/pages/account/invitations.inc.php @@ -8,7 +8,7 @@ if ($user->isAuthenticated()) { // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('invitations', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'invitations') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'invitations', @$_POST['ctoken'])) ? 1 : 0; } if ($invitation->getCountInvitations($_SESSION['USERDATA']['id']) >= $config['accounts']['invitations']['count']) { $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded the allowed invitations of ' . $config['accounts']['invitations']['count'], 'TYPE' => 'errormsg'); diff --git a/public/include/pages/account/notifications.inc.php b/public/include/pages/account/notifications.inc.php index ff2f7c28..8de15337 100644 --- a/public/include/pages/account/notifications.inc.php +++ b/public/include/pages/account/notifications.inc.php @@ -10,7 +10,7 @@ if ($user->isAuthenticated()) { // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('notifications', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editnotifs') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'editnotifs', @$_POST['ctoken'])) ? 1 : 0; } if (@$_REQUEST['do'] == 'save') { diff --git a/public/include/pages/account/unlock.inc.php b/public/include/pages/account/unlock.inc.php index 3fb4667e..9ab0551f 100644 --- a/public/include/pages/account/unlock.inc.php +++ b/public/include/pages/account/unlock.inc.php @@ -6,7 +6,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('unlockaccount', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'unlockaccount') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'unlockaccount', @$_POST['ctoken'])) ? 1 : 0; } // Confirm an account by token diff --git a/public/include/pages/account/workers.inc.php b/public/include/pages/account/workers.inc.php index 42e03b3e..89dddf9f 100644 --- a/public/include/pages/account/workers.inc.php +++ b/public/include/pages/account/workers.inc.php @@ -6,7 +6,7 @@ if ($user->isAuthenticated()) { // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('workers', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'workers') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'workers', @$_POST['ctoken'])) ? 1 : 0; } switch (@$_REQUEST['do']) { diff --git a/public/include/pages/contactform/contactform.inc.php b/public/include/pages/contactform/contactform.inc.php index 3c85df09..d734874e 100644 --- a/public/include/pages/contactform/contactform.inc.php +++ b/public/include/pages/contactform/contactform.inc.php @@ -17,7 +17,7 @@ if ($setting->getValue('recaptcha_enabled')) { // csrf if enabled $csrfenabled = ($config['csrf']['enabled'] && !in_array('contact', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'contact') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'contact', @$_POST['ctoken'])) ? 1 : 0; } if ($setting->getValue('disable_contactform')) { diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php index 2485a84c..30f3b832 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -6,7 +6,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // csrf if enabled $csrfenabled = ($config['csrf']['enabled'] && !in_array('login', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'login') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'login', @$_POST['ctoken'])) ? 1 : 0; } // ReCaptcha handling if enabled @@ -21,7 +21,6 @@ if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_ena ( (isset($_POST["recaptcha_response_field"])) ? $_POST["recaptcha_response_field"] : null ) ); $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'), $rsp->error, true)); - if (!$rsp->is_valid) $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'errormsg'); } else { $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'), null, true)); } @@ -30,36 +29,23 @@ if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_ena if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserIdByEmail($_POST['username']))) { $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info'); } else if (!empty($_POST['username']) && !empty($_POST['password'])) { - $nocsrf = 1; - $recaptchavalid = 0; - if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid) { - if ($rsp->is_valid) { - // recaptcha is enabled and valid - $recaptchavalid = 1; - } else { - // error out, invalid captcha - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: The captcha you entered was incorrect', 'TYPE' => 'errormsg'); - } - } - if ($config['csrf']['enabled'] && !in_array('login', $config['csrf']['disabled_forms'])) { - if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $csrftoken->getBasic($user->getCurrentIP(), 'login')) || (!isset($_POST['ctoken']))) { - // csrf protection is on and this token is invalid, error out -> time expired - $nocsrf = 0; - } - } // Check if recaptcha is enabled, process form data if valid - if (($setting->getValue('recaptcha_enabled') != 1 || $setting->getValue('recaptcha_enabled_logins') != 1 || $rsp->is_valid) && ($nocsrf == 1 || (!$config['csrf']['enabled'] || in_array('login', $config['csrf']['disabled_forms'])))) { - if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { - empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to']; - $port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); - $location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to; - if (!headers_sent()) header('Location: ' . $location); - exit(''); + if (!$setting->getValue('recaptcha_enabled') || !$setting->getValue('recaptcha_enabled_logins') || ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid)) { + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { + empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to']; + $port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); + $location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to; + if (!headers_sent()) header('Location: ' . $location); + exit(''); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '.$user->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'errormsg'); } } // csrf token diff --git a/public/include/pages/password/change.inc.php b/public/include/pages/password/change.inc.php index 2c3e08a0..d99ac744 100644 --- a/public/include/pages/password/change.inc.php +++ b/public/include/pages/password/change.inc.php @@ -8,7 +8,7 @@ if (!defined('SECURITY')) $csrfenabled = ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { // we have to use editaccount token because this that's where we'll get pushed here from - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editaccount') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'editaccount', @$_POST['ctoken'])) ? 1 : 0; } if (!$csrfenabled || $csrfenabled && $nocsrf) { diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php index a32ce104..4e9c18b2 100644 --- a/public/include/pages/password/reset.inc.php +++ b/public/include/pages/password/reset.inc.php @@ -6,7 +6,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // csrf stuff $csrfenabled = ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'resetpass') == @$_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'resetpass', @$_POST['ctoken'])) ? 1 : 0; } // Process password reset request diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php index 2cdf762a..ef68fe5f 100644 --- a/public/include/pages/register/register.inc.php +++ b/public/include/pages/register/register.inc.php @@ -20,7 +20,7 @@ if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_ena // csrf if enabled $csrfenabled = ($config['csrf']['enabled'] && !in_array('register', $config['csrf']['disabled_forms'])) ? 1 : 0; if ($csrfenabled) { - $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'register') == $_POST['ctoken']) ? 1 : 0; + $nocsrf = ($csrftoken->checkBasic($user->getCurrentIP(), 'register', @$_POST['ctoken'])) ? 1 : 0; } if ($setting->getValue('disable_invitations') && $setting->getValue('lock_registration')) { diff --git a/public/templates/mpos/password/change/default.tpl b/public/templates/mpos/password/change/default.tpl index f22815bf..f567b441 100644 --- a/public/templates/mpos/password/change/default.tpl +++ b/public/templates/mpos/password/change/default.tpl @@ -3,7 +3,7 @@ - {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.options.sitewide}{/if} + {if $GLOBAL.csrf.enabled && !"passreset"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

Password reset

@@ -21,7 +21,7 @@