From 9cd45a1ec1932189fc5295e0f9978b36ab5eecaa Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 27 Mar 2019 14:55:25 +0100 Subject: [PATCH] Fix/cookie auth (#5562) * prevent timed attack * add new field for storing cookie token and reverse the verification (we store the hash on our side and not client side) Backported from 9.4 26900a5e53a5ad347d20a947eb5f7e2b447fef9f Check cookie validity on glpi side Backported from 9.4 a3ab29c77c6fc7a45a7b0ef71c4442aee62acda6 Hack for DB Schema As 9.3.3 use dbschema 9.3.2, we can switch to 9.3.3 Migration detection use dbversion, so will be raised Script execution use version, which allow to keep it unchanged More 9.4 is aware of 9.3.3 (not of 9.4) --- inc/auth.class.php | 19 ++++------- inc/define.php | 2 +- inc/update.class.php | 7 +++- inc/user.class.php | 64 ++++++++++++++++++++++++++++++------ install/mysql/glpi-empty.sql | 10 +++--- install/update_932_933.php | 63 +++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 install/update_932_933.php diff --git a/inc/auth.class.php b/inc/auth.class.php index 707d7ab31..67d361653 100644 --- a/inc/auth.class.php +++ b/inc/auth.class.php @@ -500,11 +500,11 @@ class Auth extends CommonGLPI { if (count($data) === 2) { list ($cookie_id, $cookie_token) = $data; - $token = User::getToken($cookie_id, 'personal_token'); + $user = new User(); + $user->getFromDB($cookie_id); + $hash = $user->getAuthToken('cookie_token'); - if ($token !== false && Auth::checkPassword($token, $cookie_token)) { - $user = new User(); - $user->getFromDB($cookie_id); //true if $token is not false + if (Auth::checkPassword($cookie_token, $hash)) { $this->user->fields['name'] = $user->fields['name']; return true; } else { @@ -848,12 +848,7 @@ class Auth extends CommonGLPI { } if ($this->auth_succeded && $CFG_GLPI['login_remember_time'] > 0 && $remember_me) { - $token = false; - if (!empty($this->user->fields['personal_token'])) { - $token = $this->user->fields['personal_token']; - } else { - $token = User::getToken($this->user->fields['id'], 'personal_token'); - } + $token = $this->user->getAuthToken('cookie_token', true); if ($token) { //Cookie name (Allow multiple GLPI) @@ -861,11 +856,9 @@ class Auth extends CommonGLPI { //Cookie session path $cookie_path = ini_get('session.cookie_path'); - $hash = Auth::getPasswordHash($token); - $data = json_encode([ $this->user->fields['id'], - $hash, + $token, ]); //Send cookie to browser diff --git a/inc/define.php b/inc/define.php index 78125529e..22830bc05 100644 --- a/inc/define.php +++ b/inc/define.php @@ -41,7 +41,7 @@ if (substr(GLPI_VERSION, -4) === '-dev') { ); } else { //for stable version - define("GLPI_SCHEMA_VERSION", '9.3.2'); + define("GLPI_SCHEMA_VERSION", '9.3.3'); } define('GLPI_MIN_PHP', '5.6.0'); // Must also be changed in top of index.php define('GLPI_YEAR', '2018'); diff --git a/inc/update.class.php b/inc/update.class.php index fec2271c0..e3da3c9f0 100644 --- a/inc/update.class.php +++ b/inc/update.class.php @@ -431,7 +431,12 @@ class Update extends CommonGLPI { case "9.3.1": include_once "{$updir}update_931_932.php"; update931to932(); - break; + + case "9.3.2": + case "9.3.3": + // post 9.3.3 + include_once "{$updir}update_932_933.php"; + update932to933(); case GLPI_VERSION: case GLPI_SCHEMA_VERSION: diff --git a/inc/user.class.php b/inc/user.class.php index 67bfc7149..d4139edfa 100644 --- a/inc/user.class.php +++ b/inc/user.class.php @@ -4595,26 +4595,70 @@ class User extends CommonDBTM { } - /** + /** + * Get token of a user. If it does not exists then generate it. + * + * @since 9.4 + * + * @param string $field the field storing the token + * @param boolean $force_new force generation of a new token + * + * @return string|false token or false in case of error + */ + public function getAuthToken($field = 'personal_token', $force_new = false) { + global $CFG_GLPI; + + if ($this->isNewItem()) { + return false; + } + + // check date validity for cookie token + $outdated = false; + if ($field === 'cookie_token') { + $date_create = new DateTime($this->fields[$field."_date"]); + $date_expir = $date_create->add(new DateInterval('PT'.$CFG_GLPI["login_remember_time"].'S')); + + if ($date_expir < new DateTime()) { + $outdated = true; + } + } + + // token exists, is not oudated, and we may use it + if (!empty($this->fields[$field]) && !$force_new && !$outdated) { + return $this->fields[$field]; + } + + // else get a new token + $token = self::getUniqueToken($field); + + // for cookie token, we need to store it hashed + $hash = $token; + if ($field === 'cookie_token') { + $hash = Auth::getPasswordHash($token); + } + + // save this token in db + $this->update(['id' => $this->getID(), + $field => $hash, + $field . "_date" => $_SESSION['glpi_currenttime']]); + + return $token; + } + +/** * Get token of a user. If not exists generate it. * * @param integer $ID User ID * @param string $field Field storing the token + * @param boolean $force_new force generation of a new token * * @return string|boolean User token, false if user does not exist */ - static function getToken($ID, $field = 'personal_token') { + static function getToken($ID, $field = 'personal_token', $force_new = false) { $user = new self(); if ($user->getFromDB($ID)) { - if (!empty($user->fields[$field])) { - return $user->fields[$field]; - } - $token = self::getUniqueToken($field); - $user->update(['id' => $user->getID(), - $field => $token, - $field . "_date" => $_SESSION['glpi_currenttime']]); - return $user->fields[$field]; + return $user->getAuthToken($field, $force_new); } return false; diff --git a/install/mysql/glpi-empty.sql b/install/mysql/glpi-empty.sql index abfe14600..14d6976c9 100644 --- a/install/mysql/glpi-empty.sql +++ b/install/mysql/glpi-empty.sql @@ -8850,6 +8850,8 @@ CREATE TABLE `glpi_users` ( `personal_token_date` datetime DEFAULT NULL, `api_token` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `api_token_date` datetime DEFAULT NULL, + `cookie_token` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `cookie_token_date` datetime DEFAULT NULL, `display_count_on_home` int(11) DEFAULT NULL, `notification_to_myself` tinyint(1) DEFAULT NULL, `duedateok_color` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, @@ -8900,10 +8902,10 @@ CREATE TABLE `glpi_users` ( KEY `sync_field` (`sync_field`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -INSERT INTO `glpi_users` VALUES ('2','glpi','$2y$10$rXXzbc2ShaiCldwkw4AZL.n.9QSH7c0c9XJAyyjrbL9BwmWditAYm','','','','',NULL,'0',NULL,'0','20','1',NULL,'0','1','2014-06-18 08:02:24','2014-06-18 08:02:24',NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); -INSERT INTO `glpi_users` VALUES ('3','post-only','$2y$10$dTMar1F3ef5X/H1IjX9gYOjQWBR1K4bERGf4/oTPxFtJE/c3vXILm','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); -INSERT INTO `glpi_users` VALUES ('4','tech','$2y$10$.xEgErizkp6Az0z.DHyoeOoenuh0RcsX4JapBk2JMD6VI17KtB1lO','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); -INSERT INTO `glpi_users` VALUES ('5','normal','$2y$10$Z6doq4zVHkSPZFbPeXTCluN1Q/r0ryZ3ZsSJncJqkN3.8cRiN0NV.','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); +INSERT INTO `glpi_users` VALUES ('2','glpi','$2y$10$rXXzbc2ShaiCldwkw4AZL.n.9QSH7c0c9XJAyyjrbL9BwmWditAYm','','','','',NULL,'0',NULL,'0','20','1',NULL,'0','1','2014-06-18 08:02:24','2014-06-18 08:02:24',NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); +INSERT INTO `glpi_users` VALUES ('3','post-only','$2y$10$dTMar1F3ef5X/H1IjX9gYOjQWBR1K4bERGf4/oTPxFtJE/c3vXILm','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); +INSERT INTO `glpi_users` VALUES ('4','tech','$2y$10$.xEgErizkp6Az0z.DHyoeOoenuh0RcsX4JapBk2JMD6VI17KtB1lO','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); +INSERT INTO `glpi_users` VALUES ('5','normal','$2y$10$Z6doq4zVHkSPZFbPeXTCluN1Q/r0ryZ3ZsSJncJqkN3.8cRiN0NV.','','','','',NULL,'0','en_GB','0','20','1',NULL,'0','1',NULL,NULL,NULL,'0','0','0','0','0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'0',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'',NULL); ### Dump table glpi_usertitles diff --git a/install/update_932_933.php b/install/update_932_933.php new file mode 100644 index 000000000..240c5cb00 --- /dev/null +++ b/install/update_932_933.php @@ -0,0 +1,63 @@ +. + * --------------------------------------------------------------------- + */ + +/** @file +* @brief +*/ + +/** + * Update from 9.3.2 to 9.3.3 (DB schema version, introduced post 9.3.3) + * + * @return bool for success (will die for most error) +**/ +function update932to933() { + global $DB, $migration, $CFG_GLPI; + + $current_config = Config::getConfigurationValues('core'); + $updateresult = true; + $ADDTODISPLAYPREF = []; + + //TRANS: %s is the number of new version + $migration->displayTitle(sprintf(__('Update to %s'), '9.3.3')); + $migration->setVersion('9.3.3'); + + // Create a dedicated token for rememberme process + if (!$DB->fieldExists('glpi_users', 'cookie_token')) { + $migration->addField('glpi_users', 'cookie_token', 'string', ['after' => 'api_token_date']); + $migration->addField('glpi_users', 'cookie_token_date', 'datetime', ['after' => 'cookie_token']); + } + + // ************ Keep it at the end ************** + $migration->executeMigration(); + + return $updateresult; +} -- 2.20.1