CVE-2018-11406
Description
An issue was discovered in the Security component in Symfony 2.7.x before 2.7.48, 2.8.x before 2.8.41, 3.3.x before 3.3.17, 3.4.x before 3.4.11, and 4.0.x before 4.0.11. By default, a user's session is invalidated when the user is logged out. This behavior can be disabled through the invalidate_session option. In this case, CSRF tokens were not erased during logout which allowed for CSRF token fixation.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Symfony disables session invalidation on logout, causing CSRF tokens not to be cleared and enabling token fixation.
Vulnerability
An issue in the Security component of Symfony allows CSRF token fixation when the invalidate_session option is disabled. By default, Symfony invalidates the user's session upon logout, which clears CSRF tokens. However, when this behavior is disabled via the invalidate_session option, CSRF tokens are not erased during logout, leaving them valid for reuse. The affected versions are Symfony 2.7.x before 2.7.48, 2.8.x before 2.8.41, 3.3.x before 3.3.17, 3.4.x before 3.4.11, and 4.0.x before 4.0.11 [1], [2].
Exploitation
An attacker can exploit this vulnerability by obtaining a valid CSRF token from a user's session before logout (e.g., via social engineering or by performing an action that leaks the token). Since the token is not cleared on logout, the attacker can reuse the same token to perform unauthorized actions on behalf of the user after the user has logged out, provided the token is still valid and the user has not started a new session [1], [2]. The attacker does not need network access to the victim's machine but must obtain the token through other means, such as cross-site scripting or intercepted responses [2].
Impact
Successful exploitation leads to CSRF token fixation, allowing an attacker to forge requests on behalf of an authenticated user even after the user logs out. This can result in unauthorized actions such as changing account settings, initiating transactions, or modifying data, depending on the application's functionality. The impact is limited to applications where the invalidate_session option is explicitly disabled [1], [2].
Mitigation
The fix was released in Symfony versions 2.7.48, 2.8.41, 3.3.17, 3.4.11, and 4.0.11 on June 13, 2018. The patch registers a CsrfTokenClearingLogoutHandler that clears CSRF tokens during logout even when session invalidation is disabled [1]. Users should upgrade to these or later versions. Alternatively, administrators can ensure the invalidate_session option is enabled (the default) to prevent this issue [2]. No workaround exists for the base vulnerability without upgrading or re-enabling session invalidation.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
symfony/symfonyPackagist | >= 2.7.0, < 2.7.48 | 2.7.48 |
symfony/symfonyPackagist | >= 2.8.0, < 2.8.41 | 2.8.41 |
symfony/symfonyPackagist | >= 3.0.0, < 3.3.17 | 3.3.17 |
symfony/symfonyPackagist | >= 3.4.0, < 3.4.11 | 3.4.11 |
symfony/symfonyPackagist | >= 4.0.0, < 4.0.11 | 4.0.11 |
symfony/security-bundlePackagist | >= 2.7.0, < 2.7.48 | 2.7.48 |
symfony/security-bundlePackagist | >= 2.8.0, < 2.8.41 | 2.8.41 |
symfony/security-bundlePackagist | >= 3.0.0, < 3.3.17 | 3.3.17 |
symfony/security-bundlePackagist | >= 3.4.0, < 3.4.11 | 3.4.11 |
symfony/security-bundlePackagist | >= 4.0.0, < 4.0.11 | 4.0.11 |
symfony/security-httpPackagist | >= 2.7.0, < 2.7.48 | 2.7.48 |
symfony/security-httpPackagist | >= 2.8.0, < 2.8.41 | 2.8.41 |
symfony/security-httpPackagist | >= 3.0.0, < 3.3.17 | 3.3.17 |
symfony/security-httpPackagist | >= 3.4.0, < 3.4.11 | 3.4.11 |
symfony/security-httpPackagist | >= 4.0.0, < 4.0.11 | 4.0.11 |
symfony/securityPackagist | >= 2.7.0, < 2.7.48 | 2.7.48 |
symfony/securityPackagist | >= 2.8.0, < 2.8.41 | 2.8.41 |
symfony/securityPackagist | >= 3.0.0, < 3.3.17 | 3.3.17 |
symfony/securityPackagist | >= 3.4.0, < 3.4.11 | 3.4.11 |
symfony/securityPackagist | >= 4.0.0, < 4.0.11 | 4.0.11 |
Affected products
4- ghsa-coords4 versionspkg:composer/symfony/securitypkg:composer/symfony/security-bundlepkg:composer/symfony/security-httppkg:composer/symfony/symfony
>= 2.7.0, < 2.7.48+ 3 more
- (no CPE)range: >= 2.7.0, < 2.7.48
- (no CPE)range: >= 2.7.0, < 2.7.48
- (no CPE)range: >= 2.7.0, < 2.7.48
- (no CPE)range: >= 2.7.0, < 2.7.48
Patches
1319e1bdd4397security #cve-2018-11406 clear CSRF tokens when the user is logged out
15 files changed · +327 −4
src/Symfony/Bundle/SecurityBundle/composer.json+1 −1 modified@@ -18,7 +18,7 @@ "require": { "php": ">=5.3.9", "ext-xml": "*", - "symfony/security": "~2.7.47|~2.8.40", + "symfony/security": "~2.7.48|~2.8.41", "symfony/security-acl": "~2.7", "symfony/http-kernel": "~2.7" },
src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php+42 −0 added@@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Christian Flothmann <christian.flothmann@sensiolabs.de> + */ +class RegisterCsrfTokenClearingLogoutHandlerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { + return; + } + + $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); + $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); + + if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) { + return; + } + + $container->register('security.logout.handler.csrf_token_clearing', 'Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler') + ->addArgument(new Reference('security.csrf.token_storage')) + ->setPublic(false); + + $container->findDefinition('security.logout_listener')->addMethodCall('addHandler', array(new Reference('security.logout.handler.csrf_token_clearing'))); + } +}
src/Symfony/Bundle/SecurityBundle/SecurityBundle.php+2 −0 modified@@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -50,5 +51,6 @@ public function build(ContainerBuilder $container) $extension->addUserProviderFactory(new InMemoryFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); } }
src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/bundles.php+18 −0 added@@ -0,0 +1,18 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), + new SecurityBundle(), +);
src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml+26 −0 added@@ -0,0 +1,26 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + default: + form_login: + check_path: login + remember_me: true + require_previous_session: false + remember_me: + always_remember_me: true + key: key + logout: + invalidate_session: false + anonymous: ~ + stateless: true
src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/routing.yml+5 −0 added@@ -0,0 +1,5 @@ +login: + path: /login + +logout: + path: /logout
src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php+18 −0 modified@@ -31,4 +31,22 @@ public function testSessionLessRememberMeLogout() $this->assertNull($cookieJar->get('REMEMBERME')); } + + public function testCsrfTokensAreClearedOnLogout() + { + $client = $this->createClient(array('test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml')); + $client->getContainer()->get('security.csrf.token_storage')->setToken('foo', 'bar'); + + $client->request('POST', '/login', array( + '_username' => 'johannes', + '_password' => 'test', + )); + + $this->assertTrue($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); + $this->assertSame('bar', $client->getContainer()->get('security.csrf.token_storage')->getToken('foo')); + + $client->request('GET', '/logout'); + + $this->assertFalse($client->getContainer()->get('security.csrf.token_storage')->hasToken('foo')); + } }
src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php+28 −0 modified@@ -116,4 +116,32 @@ public function testRemoveExistingToken() $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); $this->assertFalse($this->storage->hasToken('token_id')); } + + public function testClearRemovesAllTokensFromTheConfiguredNamespace() + { + $this->storage->setToken('foo', 'bar'); + $this->storage->clear(); + + $this->assertFalse($this->storage->hasToken('foo')); + $this->assertArrayNotHasKey(self::SESSION_NAMESPACE, $_SESSION); + } + + public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces() + { + $_SESSION['foo']['bar'] = 'baz'; + $this->storage->clear(); + + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertArrayHasKey('bar', $_SESSION['foo']); + $this->assertSame('baz', $_SESSION['foo']['bar']); + } + + public function testClearDoesNotRemoveNonNamespacedSessionValues() + { + $_SESSION['foo'] = 'baz'; + $this->storage->clear(); + + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertSame('baz', $_SESSION['foo']); + } }
src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php+27 −0 modified@@ -129,4 +129,31 @@ public function testRemoveExistingTokenFromActiveSession() $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); } + + public function testClearRemovesAllTokensFromTheConfiguredNamespace() + { + $this->storage->setToken('foo', 'bar'); + $this->storage->clear(); + + $this->assertFalse($this->storage->hasToken('foo')); + $this->assertFalse($this->session->has(self::SESSION_NAMESPACE.'/foo')); + } + + public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces() + { + $this->session->set('foo/bar', 'baz'); + $this->storage->clear(); + + $this->assertTrue($this->session->has('foo/bar')); + $this->assertSame('baz', $this->session->get('foo/bar')); + } + + public function testClearDoesNotRemoveNonNamespacedSessionValues() + { + $this->session->set('foo', 'baz'); + $this->storage->clear(); + + $this->assertTrue($this->session->has('foo')); + $this->assertSame('baz', $this->session->get('foo')); + } }
src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php+23 −0 added@@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * @author Christian Flothmann <christian.flothmann@sensiolabs.de> + */ +interface ClearableTokenStorageInterface extends TokenStorageInterface +{ + /** + * Removes all CSRF tokens. + */ + public function clear(); +}
src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php+9 −1 modified@@ -18,7 +18,7 @@ * * @author Bernhard Schussek <bschussek@gmail.com> */ -class NativeSessionTokenStorage implements TokenStorageInterface +class NativeSessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. @@ -96,6 +96,14 @@ public function removeToken($tokenId) return $token; } + /** + * {@inheritdoc} + */ + public function clear() + { + unset($_SESSION[$this->namespace]); + } + private function startSession() { if (\PHP_VERSION_ID >= 50400) {
src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php+13 −1 modified@@ -19,7 +19,7 @@ * * @author Bernhard Schussek <bschussek@gmail.com> */ -class SessionTokenStorage implements TokenStorageInterface +class SessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. @@ -92,4 +92,16 @@ public function removeToken($tokenId) return $this->session->remove($this->namespace.'/'.$tokenId); } + + /** + * {@inheritdoc} + */ + public function clear() + { + foreach (array_keys($this->session->all()) as $key) { + if (0 === strpos($key, $this->namespace.'/')) { + $this->session->remove($key); + } + } + } }
src/Symfony/Component/Security/Http/composer.json+4 −1 modified@@ -24,9 +24,12 @@ }, "require-dev": { "symfony/routing": "~2.2", - "symfony/security-csrf": "~2.4", + "symfony/security-csrf": "~2.7.48 || ~2.8.41", "psr/log": "~1.0" }, + "conflict": { + "symfony/security-csrf": "<2.7.48 || >=2.8.0,<2.8.41 || >=3.0.0" + }, "suggest": { "symfony/security-csrf": "For using tokens to protect authentication/logout attempts", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs"
src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php+35 −0 added@@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Logout; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; + +/** + * @author Christian Flothmann <christian.flothmann@sensiolabs.de> + */ +class CsrfTokenClearingLogoutHandler implements LogoutHandlerInterface +{ + private $csrfTokenStorage; + + public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) + { + $this->csrfTokenStorage = $csrfTokenStorage; + } + + public function logout(Request $request, Response $response, TokenInterface $token) + { + $this->csrfTokenStorage->clear(); + } +}
src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php+76 −0 added@@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Logout; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler; + +class CsrfTokenClearingLogoutHandlerTest extends TestCase +{ + private $session; + private $csrfTokenStorage; + private $csrfTokenClearingLogoutHandler; + + protected function setUp() + { + $this->session = new Session(new MockArraySessionStorage()); + $this->csrfTokenStorage = new SessionTokenStorage($this->session, 'foo'); + $this->csrfTokenStorage->setToken('foo', 'bar'); + $this->csrfTokenStorage->setToken('foobar', 'baz'); + $this->csrfTokenClearingLogoutHandler = new CsrfTokenClearingLogoutHandler($this->csrfTokenStorage); + } + + public function testCsrfTokenCookieWithSameNamespaceIsRemoved() + { + $this->assertSame('bar', $this->session->get('foo/foo')); + $this->assertSame('baz', $this->session->get('foo/foobar')); + + $this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()); + + $this->assertFalse($this->csrfTokenStorage->hasToken('foo')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foobar')); + + $this->assertFalse($this->session->has('foo/foo')); + $this->assertFalse($this->session->has('foo/foobar')); + } + + public function testCsrfTokenCookieWithDifferentNamespaceIsNotRemoved() + { + $barNamespaceCsrfSessionStorage = new SessionTokenStorage($this->session, 'bar'); + $barNamespaceCsrfSessionStorage->setToken('foo', 'bar'); + $barNamespaceCsrfSessionStorage->setToken('foobar', 'baz'); + + $this->assertSame('bar', $this->session->get('foo/foo')); + $this->assertSame('baz', $this->session->get('foo/foobar')); + $this->assertSame('bar', $this->session->get('bar/foo')); + $this->assertSame('baz', $this->session->get('bar/foobar')); + + $this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()); + + $this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foo')); + $this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foobar')); + $this->assertSame('bar', $barNamespaceCsrfSessionStorage->getToken('foo')); + $this->assertSame('baz', $barNamespaceCsrfSessionStorage->getToken('foobar')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foo')); + $this->assertFalse($this->csrfTokenStorage->hasToken('foobar')); + + $this->assertFalse($this->session->has('foo/foo')); + $this->assertFalse($this->session->has('foo/foobar')); + $this->assertSame('bar', $this->session->get('bar/foo')); + $this->assertSame('baz', $this->session->get('bar/foobar')); + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
16- github.com/advisories/GHSA-g4g7-q726-v5hgghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/G4XNBMFW33H47O5TZGA7JYCVLDBCXAJV/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/UBQK7JDXIELADIPGZIOUCZKMAJM5LSBW/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WU5N2TZFNGXDGMXMPP7LZCWTFLENF6WH/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2018-11406ghsaADVISORY
- www.debian.org/security/2018/dsa-4262ghsavendor-advisoryx_refsource_DEBIANWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/security-bundle/CVE-2018-11406.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/security-http/CVE-2018-11406.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/security/CVE-2018-11406.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2018-11406.yamlghsaWEB
- github.com/symfony/symfony/commit/319e1bdd43979d9c1559497de8d69adea28ab8d1ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/G4XNBMFW33H47O5TZGA7JYCVLDBCXAJVghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UBQK7JDXIELADIPGZIOUCZKMAJM5LSBWghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WU5N2TZFNGXDGMXMPP7LZCWTFLENF6WHghsaWEB
- symfony.com/blog/cve-2018-11406-csrf-token-fixationghsax_refsource_CONFIRMWEB
- symfony.com/cve-2018-11406ghsaWEB
News mentions
0No linked articles in our index yet.