Symfony possible session fixation vulnerability
Description
Symfony is a PHP framework for web and console applications and a set of reusable PHP components. Starting in versions 5.4.21 and 6.2.7 and prior to versions 5.4.31 and 6.3.8, SessionStrategyListener does not migrate the session after every successful login. It does so only in case the logged in user changes by means of checking the user identifier. In some use cases, the user identifier doesn't change between the verification phase and the successful login, while the token itself changes from one type (partially-authenticated) to another (fully-authenticated). When this happens, the session id should be regenerated to prevent possible session fixations, which is not the case at the moment. As of versions 5.4.31 and 6.3.8, Symfony now checks the type of the token in addition to the user identifier before deciding whether the session id should be regenerated.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
symfony/security-httpPackagist | >= 5.4.21, < 5.4.31 | 5.4.31 |
symfony/security-httpPackagist | >= 6.2.7, < 6.3.8 | 6.3.8 |
symfony/symfonyPackagist | >= 5.4.21, < 5.4.31 | 5.4.31 |
symfony/symfonyPackagist | >= 6.2.7, < 6.3.8 | 6.3.8 |
Affected products
1- Range: >= 5.4.21, < 5.4.31
Patches
27467bd7e3f88security #cve-2023-46733 [Security] Fix possible session fixation when only the *token* changes (RobertMe)
2 files changed · +22 −1
src/Symfony/Component/Security/Http/EventListener/SessionStrategyListener.php+1 −1 modified@@ -48,7 +48,7 @@ public function onSuccessfulLogin(LoginSuccessEvent $event): void $user = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); - if ('' !== ($user ?? '') && $user === $previousUser) { + if ('' !== ($user ?? '') && $user === $previousUser && \get_class($token) === \get_class($previousToken)) { return; } }
src/Symfony/Component/Security/Http/Tests/EventListener/SessionStrategyListenerTest.php+21 −0 modified@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; @@ -81,6 +82,26 @@ public function testRequestWithSamePreviousUser() $this->listener->onSuccessfulLogin($event); } + public function testRequestWithSamePreviousUserButDifferentTokenType() + { + $this->configurePreviousSession(); + + $token = $this->createMock(NullToken::class); + $token->expects($this->once()) + ->method('getUserIdentifier') + ->willReturn('test'); + $previousToken = $this->createMock(UsernamePasswordToken::class); + $previousToken->expects($this->once()) + ->method('getUserIdentifier') + ->willReturn('test'); + + $this->sessionAuthenticationStrategy->expects($this->once())->method('onAuthentication')->with($this->request, $token); + + $event = new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new UserBadge('test', function () {})), $token, $this->request, null, 'main_firewall', $previousToken); + + $this->listener->onSuccessfulLogin($event); + } + private function createEvent($firewallName) { return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new UserBadge('test', function ($username) { return new InMemoryUser($username, null); })), $this->token, $this->request, null, $firewallName);
dc356499d5ce[Security] Fix possible session fixation when only the *token* changes
2 files changed · +22 −1
src/Symfony/Component/Security/Http/EventListener/SessionStrategyListener.php+1 −1 modified@@ -48,7 +48,7 @@ public function onSuccessfulLogin(LoginSuccessEvent $event): void $user = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); - if ('' !== ($user ?? '') && $user === $previousUser) { + if ('' !== ($user ?? '') && $user === $previousUser && \get_class($token) === \get_class($previousToken)) { return; } }
src/Symfony/Component/Security/Http/Tests/EventListener/SessionStrategyListenerTest.php+21 −0 modified@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; @@ -81,6 +82,26 @@ public function testRequestWithSamePreviousUser() $this->listener->onSuccessfulLogin($event); } + public function testRequestWithSamePreviousUserButDifferentTokenType() + { + $this->configurePreviousSession(); + + $token = $this->createMock(NullToken::class); + $token->expects($this->once()) + ->method('getUserIdentifier') + ->willReturn('test'); + $previousToken = $this->createMock(UsernamePasswordToken::class); + $previousToken->expects($this->once()) + ->method('getUserIdentifier') + ->willReturn('test'); + + $this->sessionAuthenticationStrategy->expects($this->once())->method('onAuthentication')->with($this->request, $token); + + $event = new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new UserBadge('test', function () {})), $token, $this->request, null, 'main_firewall', $previousToken); + + $this->listener->onSuccessfulLogin($event); + } + private function createEvent($firewallName) { return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new UserBadge('test', function ($username) { return new InMemoryUser($username, null); })), $this->token, $this->request, null, $firewallName);
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-m2wj-r6g3-fxfxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-46733ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2023-46733.yamlghsaWEB
- github.com/symfony/symfony/commit/7467bd7e3f888b333102bc664b5e02ef1e7f88b9ghsax_refsource_MISCWEB
- github.com/symfony/symfony/commit/dc356499d5ceb86f7cf2b4c7f032eca97061ed74ghsax_refsource_MISCWEB
- github.com/symfony/symfony/security/advisories/GHSA-m2wj-r6g3-fxfxghsax_refsource_CONFIRMWEB
- symfony.com/cve-2023-46733ghsaWEB
News mentions
0No linked articles in our index yet.