[TikiWiki-commits] [Git][tikiwiki/tiki][24.x] [BP][FIX] 2FA login: replace plaintext password in session with one-time token
"Elifeleti Mukisa Dan \(@Danelif\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a11a08ba5a71_381991dc7529b@gitlab-sidekiq-low-urgency-cpu-bound-v2-d7f87744c-6ddnn.mail> |
Elifeleti Mukisa Dan pushed to branch 24.x at Tiki Wiki CMS Groupware / Tiki Commits: 6d1d5467 by Elifeleti Mukisa Dan at 2026-05-23T12:36:46+00:00 [BP][FIX] 2FA login: replace plaintext password in session with one-time token --- * [BP][FIX] 2FA login: replace plaintext password in session with one-time token --- * [BP][FIX] 2FA login: replace plaintext password in session with one-time token --- * Revert "[BP][FIX] Prevent open redirect via unvalidated user-supplied URLs" This reverts commit 7a543df9fcf2a74da9a925ae3173362f7111335d. * [BP][FIX] 2FA login: replace plaintext password in session with one-time token --- * [FIX] 2FA login: replace plaintext password in session with one-time token --- * [FIX] 2FA login: replace plaintext password in session with one-time token (cherry picked from commit 2dd82889edba45ea9d91708c4f971c313dd79e5c) 2dd82889 [FIX] 2FA login: replace plaintext password in session with one-time token Co-authored-by: Danelif <[email protected]> See merge request tikiwiki/tiki!10294 (cherry picked from commit 5c8daa413e019687021d6b11d0c9e99f3eea5274) 79bc2b2c [FIX] 2FA login: replace plaintext password in session with one-time token Co-authored-by: Elifeleti Mukisa Dan <[email protected]> See merge request tikiwiki/tiki!10315 See merge request tikiwiki/tiki!10327 See merge request tikiwiki/tiki!10343 See merge request tikiwiki/tiki!10354 - - - - - 3 changed files: - lib/core/Services/User/Controller.php - tiki-change_password.php - tiki-login.php Changes: ===================================== lib/core/Services/User/Controller.php ===================================== @@ -1105,7 +1105,7 @@ class Services_User_Controller } } - private function removeUsers(array $users, $page = false, $trackerIds = [], $files = false, $referer = false) + private function removeUsers(array $users, $page = false, $trackerIds = [], $files = false) { global $user; foreach ($users as $deleteuser) { ===================================== tiki-change_password.php ===================================== @@ -127,8 +127,80 @@ if (isset($_REQUEST["change"])) { } else { $accesslib->redirect($homePageUrl); } + + // Only proceed if password change is allowed + if ($can_change_password) { + // Validate password change operation + $validation_errors = false; + if ($_REQUEST["pass"] != $_REQUEST["passAgain"]) { + Feedback::error(tra("The passwords do not match")); + $validation_errors = true; + } + // Check password policy + $polerr = $userlib->check_password_policy($_REQUEST["pass"]); + if (strlen($polerr) > 0) { + Feedback::error($polerr); + $validation_errors = true; + } + // Also check if new password matches current password hash + $current_hash = $userlib->getOne('select `hash` from `users_users` where binary `login`=?', [$user]); + if (! empty($current_hash) && password_verify($_REQUEST["pass"], $current_hash)) { + Feedback::error(tra("You can not use the same password again")); + $validation_errors = true; + } + // Validate email if provided + if (isset($_REQUEST['email'])) { + if (empty($_REQUEST['email']) || ! validate_email($_REQUEST['email'], $prefs['validateEmail'])) { + Feedback::error(tra('Your email could not be validated; make sure your email is correct')); + $validation_errors = true; + } + } + + // Only proceed with password change if validation passed + if (! $validation_errors) { + // Perform password change operation + if (isset($_REQUEST['email']) && ! empty($_REQUEST['email'])) { + $userlib->change_user_email_only($user, $_REQUEST['email']); + } + $res = $userlib->change_user_password($user, $_REQUEST["pass"]); + if ($res && $prefs['pass_history_management'] === 'y') { + $userlib->addPasswordHistory($user, $_REQUEST["pass"]); + } + + // Mark reset token as used only after successful password change + if (! empty($secure_token) && ! $is_new_user_validation && ! $must_change_password) { + $passwordResetLib = new \Tiki\Lib\Auth\PasswordResetLib(); + $passwordResetLib->markPasswordResetTokenUsed($user, $secure_token); + } + + // Handle encryption if enabled + if ($prefs['feature_user_encryption'] === 'y' && ! empty($authenticated_oldpass)) { + $cryptlib = TikiLib::lib('crypt'); + $cryptlib->onChangeUserPassword($authenticated_oldpass, $_REQUEST["pass"]); + } + + // Login user as part of the change operation + $userlib->update_expired_groups(); + $loginlib = TikiLib::lib('login'); + $loginlib->activateSession($user); + $logslib->add_log('login', 'logged from change_password', $user, '', '', $tikilib->now); + if ($jitRequest->oldpass->text() !== 'admin') { + include TIKI_PATH . '/lib/setup/default_homepage.php'; + } + $homePageUrl = $prefs['tikiIndex']; + $wizardlib = TikiLib::lib('wizard'); + $force = $user == 'admin'; + $wizardlib->onLogin($user, $homePageUrl, $force); + $accesslib = TikiLib::lib('access'); + if (! empty($prefs['url_after_validation']) && ! empty($_REQUEST['new_user_validation'])) { + $access->redirect($prefs['url_after_validation']); + } else { + $accesslib->redirect($homePageUrl); + } + } + } + // If authentication failed or validation failed, fall through to display the form } -ask_ticket('change-password'); // Display the template global $prefs; ===================================== tiki-login.php ===================================== @@ -64,6 +64,19 @@ if ($prefs['session_silent'] == 'y') { session_start(); } +if ($prefs['auth_webauthn_enabled'] === 'y' && ! empty($_SESSION['webauthn_user'])) { + $_REQUEST['user'] = $_SESSION['webauthn_user']; + $_REQUEST['pass'] = ! empty($_REQUEST['pass']) ? trim($_REQUEST['pass']) : $userlib->genPass(); +} + +$twoFaPrevalidated = false; +if ($prefs['twoFactorAuth'] === 'y' && ! empty($_SESSION['tiki_2fa_token']) && ! empty($_SESSION['tiki_2fa_username'])) { + $_REQUEST['user'] = $_SESSION['tiki_2fa_username']; + $twoFaPrevalidated = true; + unset($_SESSION['tiki_2fa_token']); + unset($_SESSION['tiki_2fa_username']); +} + // Remember where user is logging in from and send them back later; using session variable for those of us who use WebISO services // Note that login from will always be a complete URL (http://...) if (! isset($_SESSION['loginfrom']) && isset($_SERVER['HTTP_REFERER']) && ! preg_match('|/login|', $_SERVER['HTTP_REFERER']) && ! preg_match('|logout|', $_SERVER['HTTP_REFERER'])) { @@ -276,7 +289,14 @@ if ( } } else { // Verify user is valid - $ret = $userlib->validate_user($requestedUser, $pass); + if ($twoFaPrevalidated) { + // Password was already validated before the 2FA prompt; use a token instead of re-checking + $ret = [true, $requestedUser, null, null]; + } elseif ($prefs['auth_webauthn_enabled'] === 'y' && ! empty($_SESSION['webauthn_user']) && $_SESSION['webauthn_user'] === $requestedUser) { + $ret = [$userlib->update_lastlogin($requestedUser), $requestedUser, USER_VALID]; + } else { + $ret = $userlib->validate_user($requestedUser, $pass); + } if (count($ret) == 3) { $ret[] = null; } @@ -299,14 +319,54 @@ if ( } } } elseif ($isvalid) { - $twoFactorSecret = $userlib->get_2_factor_secret($requestedUser); - if ($prefs['twoFactorAuth'] == 'y' && ! empty($twoFactorSecret) && ! $userlib->validate_two_factor($twoFactorSecret, $_REQUEST["twoFactorAuthCode"], $requestedUser)) { - $error = TWO_FA_INCORRECT; - $isvalid = false; - $smarty->assign('twoFactorForm', 'y'); - } else { - $isdue = $userlib->is_due($requestedUser, $method); - $user = $requestedUser; + try { + $requireMfa = false; + $twoFactorSecret = null; + + if (($prefs['twoFactorAuth'] ?? 'n') === 'y') { + $requireMfa = TwoFactorAuth::isMFARequired($requestedUser); + $twoFactorSecret = TwoFactorAuth::get2FactorSecret($requestedUser); + } + + if ( + ($prefs['twoFactorAuth'] ?? 'n') === 'y' + && isset($_REQUEST['login_mode']) + && $_REQUEST['login_mode'] == 'popup' + && $requireMfa + && ! empty($twoFactorSecret) + ) { + $_SESSION['tiki_2fa_token'] = bin2hex(random_bytes(32)); + $_SESSION['tiki_2fa_username'] = $_REQUEST['user']; + $params = '&create2FaCodeNormalLogin&tiki_username=' . urlencode($_REQUEST['user']); + header('Location: ' . $base_url . 'tiki-login_scr.php?twoFactorForm' . $params); + exit; + } + + if (($prefs['twoFactorAuth'] ?? 'n') === 'y' && $requireMfa && ! empty($twoFactorSecret)) { + $twoFactorAuth = TwoFactorAuth::getTwoFactorAuth(); + $is2FaPass = $twoFactorAuth->validateCode($requestedUser, $_REQUEST['twoFactorAuthCode']); + if (! $is2FaPass) { + $error = TWO_FA_INCORRECT; + $smarty->assign('showTwoFactorForm', 'y'); + $_SESSION['tiki_2fa_token'] = bin2hex(random_bytes(32)); + $_SESSION['tiki_2fa_username'] = $_REQUEST['user']; + $smarty->assign('create2FaCodeNormalLogin', "y"); + $smarty->assign('error_login', $error); + $smarty->assign('mid', 'tiki-login.tpl'); + $smarty->display('tiki.tpl'); + exit; + } + } + + if ($isvalid) { + if ($requireMfa && ($prefs['twoFactorAuth'] ?? 'n') === 'y') { + $userlib->updateLastMFADate($requestedUser); + } + $isdue = $userlib->is_due($requestedUser, $method); + $user = $requestedUser; + } + } catch (TwoFactorAuthException $e) { + Feedback::errorAndDie($e->getMessage(), \Laminas\Http\Response::STATUS_CODE_409); } } } View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/6d1d5467782573a2bbeef80e37dd697c556babdc -- View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/6d1d5467782573a2bbeef80e37dd697c556babdc You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help _______________________________________________ TikiWiki-cvs mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs