diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 1c8dba45..5bf2652f 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -53,6 +53,7 @@ require_once(CLASS_DIR . '/bitcoinwrapper.class.php'); require_once(CLASS_DIR . '/monitoring.class.php'); require_once(CLASS_DIR . '/notification.class.php'); require_once(CLASS_DIR . '/user.class.php'); +require_once(CLASS_DIR . '/csrftoken.class.php'); require_once(CLASS_DIR . '/invitation.class.php'); require_once(CLASS_DIR . '/share.class.php'); require_once(CLASS_DIR . '/worker.class.php'); diff --git a/public/include/classes/base.class.php b/public/include/classes/base.class.php index d3dd18c1..d8bb23bb 100644 --- a/public/include/classes/base.class.php +++ b/public/include/classes/base.class.php @@ -73,6 +73,9 @@ class Base { public function setTokenType($tokentype) { $this->tokentype = $tokentype; } + public function setCSRFToken($token) { + $this->CSRFToken = $token; + } public function setShare($share) { $this->share = $share; } diff --git a/public/include/classes/csrftoken.class.php b/public/include/classes/csrftoken.class.php new file mode 100644 index 00000000..e061e2e3 --- /dev/null +++ b/public/include/classes/csrftoken.class.php @@ -0,0 +1,45 @@ +salty; + $lead = $this->config['csrf']['options']['leadtime']; + if ($lead >= 11) { $lead = 10; } + if ($lead <= 0) { $lead = 3; } + if ($minute == 59 && $second > (60-$lead)) { + $minute = 0; + $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; + } + $seed = $seed.$month.$day.$user.$type.$year.$hour.$minute.$seed; + return $this->getHash($seed); + } + + private function getHash($string) { + return hash('sha256', $this->salty.$string.$this->salt); + } +} + +$csrftoken = new CSRFToken(); +$csrftoken->setDebug($debug); +$csrftoken->setMysql($mysqli); +$csrftoken->setSalt(SALT); +$csrftoken->setSalty(SALTY); +$csrftoken->setMail($mail); +$csrftoken->setUser($user); +$csrftoken->setToken($oToken); +$csrftoken->setConfig($config); +$csrftoken->setErrorCodes($aErrorCodes); +?> \ No newline at end of file diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 80cc346a..a9fe3f97 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -794,26 +794,36 @@ class User extends Base { } /** - * Gets the current CSRF token for this user/type setting and time chunk - * @param string User; for hash seed, if username isn't available use IP - * @param string Type of token; for hash seed, should be unique per page/use - * @return string CSRF token + * Convenience function to get IP address, no params is the same as REMOTE_ADDR + * @param trustremote bool must be FALSE to checkclient or checkforwarded + * @param checkclient bool check HTTP_CLIENT_IP for a valid ip first + * @param checkforwarded bool check HTTP_X_FORWARDED_FOR for a valid ip first + * @return string IP address */ - public function getCSRFToken($user, $type) { - $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]; - $seed = $this->salty; - $lead = $this->config['csrf']['options']['leadtime']; - if ($lead >= 11) { $lead = 10; } - if ($lead <= 0) { $lead = 3; } - if ($minute == 59 && $second > (60-$lead)) { - $minute = 0; - $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; + public function getCurrentIP($trustremote=true, $checkclient=false, $checkforwarded=false) { + $client = (isset($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : false; + $fwd = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : false; + $remote = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : @$_SERVER['REMOTE_ADDR']; + // shared internet + if (filter_var($client, FILTER_VALIDATE_IP) && !$trustremote && $checkclient) { + return $client; + } else if (strpos($fwd, ',') !== false && !$trustremote && $checkforwarded) { + // multiple proxies + $ips = explode(',', $fwd); + $path = array(); + foreach ($ips as $ip) { + if (filter_var($ip, FILTER_VALIDATE_IP)) { + $path[] = $ip; + } + } + return array_pop($path); + } else if (filter_var($fwd, FILTER_VALIDATE_IP) && !$trustremote && $checkforwarded) { + // single + return $fwd; + } else { + // as usual + return $remote; } - $seed = $seed.$month.$day.$user.$type.$year.$hour.$minute.$seed; - return $this->getHash($seed); } } @@ -822,7 +832,6 @@ $user = new User(); $user->setDebug($debug); $user->setMysql($mysqli); $user->setSalt(SALT); -$user->setSalty(SALTY); $user->setSmarty($smarty); $user->setConfig($config); $user->setMail($mail); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 7c9eebba..b5e1ef0d 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -135,15 +135,18 @@ $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, 1-10s - * login = Use and check CSRF tokens for the login forms + * sitewide = Require a valid CSRF token for all forms, does not override specific form settings + * leadtime = Length of time in seconds to give as leeway between minute switches + * login = Use and check login-specific CSRF token * * Default: * enabled = true + * sitewide = true * leadtime = 3 * login = true */ $config['csrf']['enabled'] = true; +$config['csrf']['sitewide'] = true; $config['csrf']['options']['leadtime'] = 3; $config['csrf']['forms']['login'] = true; diff --git a/public/include/pages/home.inc.php b/public/include/pages/home.inc.php index 4dffeb8a..861e24be 100644 --- a/public/include/pages/home.inc.php +++ b/public/include/pages/home.inc.php @@ -25,7 +25,7 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { // csrf token - update if it's enabled $token = ''; if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); + $token = $csrftoken->getBasic($user->getCurrentIP(), 'login'); } // Load news entries for Desktop site and unauthenticated users $smarty->assign("CONTENT", "default.tpl"); diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php index 7e43ae5a..9442d10f 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -3,7 +3,6 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); - // ReCaptcha handling if enabled if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins')) { require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); @@ -37,7 +36,7 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST } } if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login')) || (!isset($_POST['ctoken']))) { + 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; } @@ -61,7 +60,7 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST // csrf token - update if it's enabled $token = ''; if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); + $token = $csrftoken->getBasic($user->getCurrentIP(), 'login'); } // Load login template