From 4a41c71b8c7b4622633ee4b6ce1da065a7158760 Mon Sep 17 00:00:00 2001
From: Torben Hansen <derhansen@gmail.com>
Date: Tue, 13 Dec 2022 10:20:03 +0100
Subject: [PATCH] [SECURITY] Destroy user sessions on password change

The password reset process for TYPO3 backend and
frontend users does not destroy possible existing
user sessions after the password has been changed.

With this patch, all existing user sessions are
destroyed when the password is changed in the
password reset process.

Resolves: #98462
Releases: main, 11.5, 10.4
Change-Id: I6744bfcf7cae56b4e525f2e0f9a44d06cf14396c
Security-Bulletin: TYPO3-CORE-SA-2022-014
Security-References: CVE-2022-23502
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77091
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
---
 .../Classes/Authentication/PasswordReset.php        | 13 +++++++++++++
 .../Controller/PasswordRecoveryController.php       | 13 +++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/typo3/sysext/backend/Classes/Authentication/PasswordReset.php b/typo3/sysext/backend/Classes/Authentication/PasswordReset.php
index 97c452f96942..6aec0a3a9818 100644
--- a/typo3/sysext/backend/Classes/Authentication/PasswordReset.php
+++ b/typo3/sysext/backend/Classes/Authentication/PasswordReset.php
@@ -39,6 +39,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Mail\FluidEmail;
 use TYPO3\CMS\Core\Mail\Mailer;
+use TYPO3\CMS\Core\Session\SessionManager;
 use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
 use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
 use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
@@ -348,6 +349,8 @@ class PasswordReset implements LoggerAwareInterface
             ->getConnectionForTable('be_users')
             ->update('be_users', ['password_reset_token' => '', 'password' => $this->getHasher()->getHashedPassword($newPassword)], ['uid' => $userId]);
 
+        $this->invalidateUserSessions($userId);
+
         $this->logger->info('Password reset successful for user {user_id)', ['user_id' => $userId]);
         $this->log(
             'Password reset successful for user %s',
@@ -498,4 +501,14 @@ class PasswordReset implements LoggerAwareInterface
             ->executeQuery()
             ->fetchOne();
     }
+
+    /**
+     * Invalidate all backend user sessions by given user id
+     */
+    protected function invalidateUserSessions(int $userId): void
+    {
+        $sessionManager = GeneralUtility::makeInstance(SessionManager::class);
+        $sessionBackend = $sessionManager->getSessionBackend('BE');
+        $sessionManager->invalidateAllSessionsByUserId($sessionBackend, $userId);
+    }
 }
diff --git a/typo3/sysext/felogin/Classes/Controller/PasswordRecoveryController.php b/typo3/sysext/felogin/Classes/Controller/PasswordRecoveryController.php
index f76c5f8b54c2..c89c39b71196 100644
--- a/typo3/sysext/felogin/Classes/Controller/PasswordRecoveryController.php
+++ b/typo3/sysext/felogin/Classes/Controller/PasswordRecoveryController.php
@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 use TYPO3\CMS\Core\Messaging\AbstractMessage;
+use TYPO3\CMS\Core\Session\SessionManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Error\Error;
 use TYPO3\CMS\Extbase\Error\Result;
@@ -227,7 +228,9 @@ class PasswordRecoveryController extends AbstractLoginFormController
             return $hashedPassword;
         }
 
+        $user = $this->userRepository->findOneByForgotPasswordHash(GeneralUtility::hmac($hash));
         $this->userRepository->updatePasswordAndInvalidateHash(GeneralUtility::hmac($hash), $hashedPassword);
+        $this->invalidateUserSessions($user['uid']);
 
         $this->addFlashMessage($this->getTranslation('change_password_done_message'));
 
@@ -331,4 +334,14 @@ class PasswordRecoveryController extends AbstractLoginFormController
             true
         );
     }
+
+    /**
+     * Invalidate all frontend user sessions by given user id
+     */
+    protected function invalidateUserSessions(int $userId): void
+    {
+        $sessionManager = GeneralUtility::makeInstance(SessionManager::class);
+        $sessionBackend = $sessionManager->getSessionBackend('FE');
+        $sessionManager->invalidateAllSessionsByUserId($sessionBackend, $userId);
+    }
 }
-- 
GitLab