Cleaned up login page logic a bit more

Fixed up CSRF tokens so rollover minutes/hours are now checked and valid
This commit is contained in:
xisi 2014-01-23 10:43:32 -05:00
parent 33cfa5667d
commit a3314fa81e
12 changed files with 82 additions and 66 deletions

View File

@ -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

View File

@ -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();
/**

View File

@ -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()) {

View File

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

View File

@ -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') {

View File

@ -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

View File

@ -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']) {

View File

@ -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')) {

View File

@ -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('<meta http-equiv="refresh" content="0; url=' . htmlspecialchars($location) . '"/>');
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('<meta http-equiv="refresh" content="0; url=' . htmlspecialchars($location) . '"/>');
} 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

View File

@ -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) {

View File

@ -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

View File

@ -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')) {