Cookie persistence in Symfony
Description
Symfony/SecurityBundle is the security system for Symfony, a PHP framework for web and console applications and a set of reusable PHP components. Since the rework of the Remember me cookie in version 5.3.0, the cookie is not invalidated when the user changes their password. Attackers can therefore maintain their access to the account even if the password is changed as long as they have had the chance to login once and get a valid remember me cookie. Starting with version 5.3.12, Symfony makes the password part of the signature by default. In that way, when the password changes, then the cookie is not valid anymore.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
symfony/security-bundlePackagist | >= 5.3.0, < 5.3.12 | 5.3.12 |
symfony/symfonyPackagist | >= 5.3.0, < 5.3.12 | 5.3.12 |
Affected products
1- Range: >= 5.3.0, < 5.3.12
Patches
136a808b857cd[SecurityBundle] Default signature_properties to the previous behavior
3 files changed · +41 −10
src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php+1 −0 modified@@ -208,6 +208,7 @@ public function addConfiguration(NodeDefinition $node) ->requiresAtLeastOneElement() ->info('An array of properties on your User that are used to sign the remember-me cookie. If any of these change, all existing cookies will become invalid.') ->example(['email', 'password']) + ->defaultValue(['password']) ->end() ->arrayNode('token_provider') ->beforeNormalization()
src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/UserChangingUserProvider.php+15 −8 modified@@ -21,33 +21,40 @@ class UserChangingUserProvider implements UserProviderInterface { private $inner; + public static $changePassword = false; + public function __construct(InMemoryUserProvider $inner) { $this->inner = $inner; } public function loadUserByUsername($username) { - return $this->inner->loadUserByUsername($username); + return $this->changeUser($this->inner->loadUserByUsername($username)); } public function loadUserByIdentifier(string $userIdentifier): UserInterface { - return $this->inner->loadUserByIdentifier($userIdentifier); + return $this->changeUser($this->inner->loadUserByIdentifier($userIdentifier)); } public function refreshUser(UserInterface $user) { - $user = $this->inner->refreshUser($user); - - $alterUser = \Closure::bind(function (InMemoryUser $user) { $user->password = 'foo'; }, null, class_exists(User::class) ? User::class : InMemoryUser::class); - $alterUser($user); - - return $user; + return $this->changeUser($this->inner->refreshUser($user)); } public function supportsClass($class) { return $this->inner->supportsClass($class); } + + private function changeUser(UserInterface $user): UserInterface + { + if (self::$changePassword) { + $alterUser = \Closure::bind(function (InMemoryUser $user) { $user->password = 'changed!'; }, null, class_exists(User::class) ? User::class : InMemoryUser::class); + $alterUser($user); + } + + return $user; + } }
src/Symfony/Bundle/SecurityBundle/Tests/Functional/RememberMeTest.php+25 −2 modified@@ -11,8 +11,15 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\RememberMeBundle\Security\UserChangingUserProvider; + class RememberMeTest extends AbstractWebTestCase { + protected function setUp(): void + { + UserChangingUserProvider::$changePassword = false; + } + /** * @dataProvider provideConfigs */ @@ -51,11 +58,19 @@ public function testUserChangeClearsCookie() $this->assertSame(302, $client->getResponse()->getStatusCode()); $cookieJar = $client->getCookieJar(); - $this->assertNotNull($cookieJar->get('REMEMBERME')); + $this->assertNotNull($cookie = $cookieJar->get('REMEMBERME')); + + UserChangingUserProvider::$changePassword = true; + // change password (through user provider), this deauthenticates the session $client->request('GET', '/profile'); $this->assertRedirect($client->getResponse(), '/login'); $this->assertNull($cookieJar->get('REMEMBERME')); + + // restore the old remember me cookie, it should no longer be valid + $cookieJar->set($cookie); + $client->request('GET', '/profile'); + $this->assertRedirect($client->getResponse(), '/login'); } public function testSessionLessRememberMeLogout() @@ -121,11 +136,19 @@ public function testLegacyUserChangeClearsCookie() $this->assertSame(302, $client->getResponse()->getStatusCode()); $cookieJar = $client->getCookieJar(); - $this->assertNotNull($cookieJar->get('REMEMBERME')); + $this->assertNotNull($cookie = $cookieJar->get('REMEMBERME')); + + UserChangingUserProvider::$changePassword = true; + // change password (through user provider), this deauthenticates the session $client->request('GET', '/profile'); $this->assertRedirect($client->getResponse(), '/login'); $this->assertNull($cookieJar->get('REMEMBERME')); + + // restore the old remember me cookie, it should no longer be valid + $cookieJar->set($cookie); + $client->request('GET', '/profile'); + $this->assertRedirect($client->getResponse(), '/login'); } /**
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
9- github.com/advisories/GHSA-qw36-p97w-vcqrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-41268ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/security-bundle/CVE-2021-41268.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2021-41268.yamlghsaWEB
- github.com/symfony/symfony/commit/36a808b857cd3240244f4b224452fb1e70dc6dfcghsax_refsource_MISCWEB
- github.com/symfony/symfony/pull/44243ghsax_refsource_MISCWEB
- github.com/symfony/symfony/releases/tag/v5.3.12ghsax_refsource_MISCWEB
- github.com/symfony/symfony/security/advisories/GHSA-qw36-p97w-vcqrghsax_refsource_CONFIRMWEB
- symfony.com/cve-2021-41268ghsaWEB
News mentions
0No linked articles in our index yet.