CVE-2022-24895 (published 2023-02-03T22:15Z)

Symfony is a PHP framework for web and console applications and a set of reusable PHP components. When authenticating users Symfony by default regenerates the session ID upon login, but preserves the rest of session attributes. Because this does not clear CSRF tokens upon login, this might enables same-site attackers to bypass the CSRF protection mechanism by performing an attack similar to a session-fixation. This issue has been fixed in the 4.4 branch. \n\n

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-384
cwe_name: Session Fixation
description: Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
extended_description: 
            Such a scenario is commonly observed when:
            
               A web application authenticates a user without first invalidating the existing session, thereby continuing to use the session already associated with the user.
               An attacker is able to force a known session identifier on a user so that, once the user authenticates, the attacker has access to the authenticated session.
               The application or container uses predictable session identifiers.
	    
	    In the generic exploit of session fixation vulnerabilities, an attacker creates a new session on a web application and records the associated session identifier. The attacker then causes the victim to associate, and possibly authenticate, against the server using that session identifier, giving the attacker access to the user's account through the active session.
         
         

Fix information:
repo_url: https://github.com/symfony/security-bundle
commit_details: repo_url: https://github.com/symfony/security-bundle
author: Nicolas Grekas
author_date: 2023-01-23 19:43:46+01:00
msg: [Security/Http] Remove CSRF tokens from storage on successful login
repository: repo_url: https://github.com/symfony/security-bundle
repo_name: symfony/security-bundle
description: Provides a tight integration of the Security component into the Symfony full-stack framework
repo_language: PHP
owner: symfony
file_changes: filename: security.xml
change_type: ModificationType.MODIFY
diff: @@ -63,6 +63,7 @@
 
         <service id="security.authentication.session_strategy" class="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy">
             <argument>%security.authentication.session_strategy.strategy%</argument>
+            <argument type="service" id="security.csrf.token_storage" on-invalid="ignore" />
         </service>
         <service id="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface" alias="security.authentication.session_strategy" />
 

programming_language: PHP
filename: CsrfFormLoginTest.php
change_type: ModificationType.MODIFY
diff: @@ -19,12 +19,15 @@ class CsrfFormLoginTest extends AbstractWebTestCase
     public function testFormLoginAndLogoutWithCsrfTokens($config)
     {
         $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]);
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $form = $client->request('GET', '/login')->selectButton('login')->form();
         $form['user_login[username]'] = 'johannes';
         $form['user_login[password]'] = 'test';
         $client->submit($form);
 
+        $this->assertFalse(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
+
         $this->assertRedirect($client->getResponse(), '/profile');
 
         $crawler = $client->followRedirect();
@@ -48,11 +51,14 @@ class CsrfFormLoginTest extends AbstractWebTestCase
     public function testFormLoginWithInvalidCsrfToken($config)
     {
         $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]);
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $form = $client->request('GET', '/login')->selectButton('login')->form();
         $form['user_login[_token]'] = '';
         $client->submit($form);
 
+        $this->assertTrue(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
+
         $this->assertRedirect($client->getResponse(), '/login');
 
         $text = $client->followRedirect()->text(null, true);

programming_language: PHP
filename: LogoutTest.php
change_type: ModificationType.MODIFY
diff: @@ -36,15 +36,13 @@ class LogoutTest extends AbstractWebTestCase
     public function testCsrfTokensAreClearedOnLogout()
     {
         $client = $this->createClient(['test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml']);
-        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $client->request('POST', '/login', [
             '_username' => 'johannes',
             '_password' => 'test',
         ]);
 
-        $this->assertTrue(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
-        $this->assertSame('bar', static::$container->get('security.csrf.token_storage')->getToken('foo'));
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $client->request('GET', '/logout');
 

programming_language: PHP
filename: composer.json
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,7 @@
         "symfony/security-core": "^4.4",
         "symfony/security-csrf": "^4.2|^5.0",
         "symfony/security-guard": "^4.2|^5.0",
-        "symfony/security-http": "^4.4.5"
+        "symfony/security-http": "^4.4.50"
     },
     "require-dev": {
         "doctrine/annotations": "^1.10.4",

programming_language: TypeScript
repo_url: https://github.com/symfony/symfony
commit_details: repo_url: https://github.com/symfony/symfony
author: Nicolas Grekas
author_date: 2023-01-23 19:43:46+01:00
msg: [Security/Http] Remove CSRF tokens from storage on successful login
repository: repo_url: https://github.com/symfony/symfony
repo_name: symfony/symfony
description: The Symfony PHP framework
repo_language: PHP
owner: symfony
file_changes: filename: security.xml
change_type: ModificationType.MODIFY
diff: @@ -63,6 +63,7 @@
 
         <service id="security.authentication.session_strategy" class="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy">
             <argument>%security.authentication.session_strategy.strategy%</argument>
+            <argument type="service" id="security.csrf.token_storage" on-invalid="ignore" />
         </service>
         <service id="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface" alias="security.authentication.session_strategy" />
 

programming_language: PHP
filename: CsrfFormLoginTest.php
change_type: ModificationType.MODIFY
diff: @@ -19,12 +19,15 @@ class CsrfFormLoginTest extends AbstractWebTestCase
     public function testFormLoginAndLogoutWithCsrfTokens($config)
     {
         $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]);
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $form = $client->request('GET', '/login')->selectButton('login')->form();
         $form['user_login[username]'] = 'johannes';
         $form['user_login[password]'] = 'test';
         $client->submit($form);
 
+        $this->assertFalse(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
+
         $this->assertRedirect($client->getResponse(), '/profile');
 
         $crawler = $client->followRedirect();
@@ -48,11 +51,14 @@ class CsrfFormLoginTest extends AbstractWebTestCase
     public function testFormLoginWithInvalidCsrfToken($config)
     {
         $client = $this->createClient(['test_case' => 'CsrfFormLogin', 'root_config' => $config]);
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $form = $client->request('GET', '/login')->selectButton('login')->form();
         $form['user_login[_token]'] = '';
         $client->submit($form);
 
+        $this->assertTrue(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
+
         $this->assertRedirect($client->getResponse(), '/login');
 
         $text = $client->followRedirect()->text(null, true);

programming_language: PHP
filename: LogoutTest.php
change_type: ModificationType.MODIFY
diff: @@ -36,15 +36,13 @@ class LogoutTest extends AbstractWebTestCase
     public function testCsrfTokensAreClearedOnLogout()
     {
         $client = $this->createClient(['test_case' => 'LogoutWithoutSessionInvalidation', 'root_config' => 'config.yml']);
-        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $client->request('POST', '/login', [
             '_username' => 'johannes',
             '_password' => 'test',
         ]);
 
-        $this->assertTrue(static::$container->get('security.csrf.token_storage')->hasToken('foo'));
-        $this->assertSame('bar', static::$container->get('security.csrf.token_storage')->getToken('foo'));
+        static::$container->get('security.csrf.token_storage')->setToken('foo', 'bar');
 
         $client->request('GET', '/logout');
 

programming_language: PHP
filename: composer.json
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,7 @@
         "symfony/security-core": "^4.4",
         "symfony/security-csrf": "^4.2|^5.0",
         "symfony/security-guard": "^4.2|^5.0",
-        "symfony/security-http": "^4.4.5"
+        "symfony/security-http": "^4.4.50"
     },
     "require-dev": {
         "doctrine/annotations": "^1.10.4",

programming_language: TypeScript
filename: SessionAuthenticationStrategy.php
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Session;
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
 
 /**
  * The default session strategy implementation.
@@ -31,10 +32,15 @@ class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInte
     public const INVALIDATE = 'invalidate';
 
     private $strategy;
+    private $csrfTokenStorage = null;
 
-    public function __construct(string $strategy)
+    public function __construct(string $strategy, ClearableTokenStorageInterface $csrfTokenStorage = null)
     {
         $this->strategy = $strategy;
+
+        if (self::MIGRATE === $strategy) {
+            $this->csrfTokenStorage = $csrfTokenStorage;
+        }
     }
 
     /**
@@ -47,10 +53,12 @@ class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInte
                 return;
 
             case self::MIGRATE:
-                // Note: this logic is duplicated in several authentication listeners
-                // until Symfony 5.0 due to a security fix with BC compat
                 $request->getSession()->migrate(true);
 
+                if ($this->csrfTokenStorage) {
+                    $this->csrfTokenStorage->clear();
+                }
+
                 return;
 
             case self::INVALIDATE:

programming_language: PHP
filename: SessionAuthenticationStrategyTest.php
change_type: ModificationType.MODIFY
diff: @@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\Session;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
 use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
 use Symfony\Component\Security\Http\Tests\Fixtures\TokenInterface;
 
@@ -57,6 +58,18 @@ class SessionAuthenticationStrategyTest extends TestCase
         $strategy->onAuthentication($this->getRequest($session), $this->createMock(TokenInterface::class));
     }
 
+    public function testCsrfTokensAreCleared()
+    {
+        $session = $this->createMock(SessionInterface::class);
+        $session->expects($this->once())->method('migrate')->with($this->equalTo(true));
+
+        $csrfStorage = $this->createMock(ClearableTokenStorageInterface::class);
+        $csrfStorage->expects($this->once())->method('clear');
+
+        $strategy = new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE, $csrfStorage);
+        $strategy->onAuthentication($this->getRequest($session), $this->createMock(TokenInterface::class));
+    }
+
     private function getRequest($session = null)
     {
         $request = $this->createMock(Request::class);

programming_language: PHP