Merge pull request #1551 from xisi/csrf-improvements
[FIXES] CSRF tokens & login cleanup
This commit is contained in:
commit
659c203c06
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
/**
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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']) {
|
||||
|
||||
@ -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')) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')) {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<input type="hidden" name="token" value="{$smarty.request.token|escape}">
|
||||
<input type="hidden" name="page" value="{$smarty.request.page|escape}">
|
||||
<input type="hidden" name="action" value="{$smarty.request.action|escape}">
|
||||
{if $GLOBAL.csrf.enabled && $GLOBAL.csrf.options.sitewide}<input type="hidden" name="ctoken" value="{$CTOKEN|escape}" />{/if}
|
||||
{if $GLOBAL.csrf.enabled && !"passreset"|in_array:$GLOBAL.csrf.disabled_forms}<input type="hidden" name="ctoken" value="{$CTOKEN|escape}" />{/if}
|
||||
|
||||
<input type="hidden" name="do" value="resetPassword">
|
||||
<header><h3>Password reset</h3></header>
|
||||
@ -21,7 +21,7 @@
|
||||
<footer>
|
||||
{nocache}
|
||||
<input type="hidden" name="cp_token" value="{$smarty.request.cp_token|escape|default:""}">
|
||||
{if $GLOBAL.csrf.enabled && $GLOBAL.csrf.options.sitewide}<input type="hidden" name="ctoken" value="{$CTOKEN|escape}" />{/if}
|
||||
{if $GLOBAL.csrf.enabled && !"passreset"|in_array:$GLOBAL.csrf.disabled_forms}<input type="hidden" name="ctoken" value="{$CTOKEN|escape}" />{/if}
|
||||
<input type="hidden" name="utype" value="change_pw">
|
||||
{if $GLOBAL.twofactor.enabled && $GLOBAL.twofactor.options.changepw}
|
||||
{if $CHANGEPASSSENT == 1 && $CHANGEPASSUNLOCKED == 1}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user