CVE-2021-32693: Improper Authentication (CWE-287)
value: Symfony is a PHP framework for web and console applications and a set of reusable PHP components. A vulnerability related to firewall authentication is in Symfony starting with version 5.3.0 and prior to 5.3.2. When an application defines multiple firewalls, the token authenticated by one of the firewalls was available for all other firewalls. This could be abused when the application defines different providers for each part of the application, in such a situation, a user authenticated on a part of the application could be considered authenticated on the rest of the application. Starting in version 5.3.2, a patch ensures that the authenticated token is only available for the firewall that generates it.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 8.8
Published: 2021-06-17T23:15Z
Repository: https://github.com/symfony/symfony
Language: PHP
Files changed: src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticatorTest.php, src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml, src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/routing.yml, src/Symfony/Bundle/SecurityBundle/composer.json, src/Symfony/Component/Security/Http/Firewall/ContextListener.php, src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
Commit date: 2021-06-16 13:32:12 +0200
Commit message: Only trigger for the correct firewall in ContextListener::onKernelResponse()

Diff: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticatorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticatorTest.php
index beb12a0b4d6..69f64693c85 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticatorTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticatorTest.php
@@ -85,6 +85,19 @@ class AuthenticatorTest extends AbstractWebTestCase
     public function provideEmailsWithFirewalls()
     {
         yield ['jane@example.org', 'main'];
         yield ['john@example.org', 'custom'];
     }
+
+    public function testMultipleFirewalls()
+    {
+        $client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'multiple_firewalls.yml']);
+
+        $client->request('POST', '/firewall1/login', [
+            '_username' => 'jane@example.org',
+            '_password' => 'test',
+        ]);
+
+        $client->request('GET', '/firewall2/profile');
+        $this->assertResponseRedirects('http://localhost/login');
+    }
 }
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml
new file mode 100644
index 00000000000..655a8d83d4d
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml
@@ -0,0 +1,17 @@
+imports:
+- { resource: ./config.yml }
+- { resource: ./security.yml }
+
+security:
+    enable_authenticator_manager: true
+    firewalls:
+        firewall1:
+            pattern: /firewall1
+            provider: in_memory
+            form_login:
+                check_path: /firewall1/login
+        firewall2:
+            pattern: /firewall2
+            provider: in_memory2
+            form_login:
+                check_path: /firewall2/login
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/routing.yml
index 35662387dcf..4796a3f6bc0 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/routing.yml
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/routing.yml
@@ -16,5 +16,13 @@ security_main_profile:
     defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\SecurityController::profileAction }
 
 security_custom_profile:
     path:     /custom/user_profile
     defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\SecurityController::profileAction }
+
+firewall1_login:
+    path: /firewall1/login
+
+firewall2_profile:
+    path: /firewall2/profile
+    defaults:
+        _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ProfileController
diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json
index 1ebe4c7a868..5e3d13d072a 100644
--- a/src/Symfony/Bundle/SecurityBundle/composer.json
+++ b/src/Symfony/Bundle/SecurityBundle/composer.json
@@ -27,11 +27,11 @@
         "symfony/password-hasher": "^5.3",
         "symfony/polyfill-php80": "^1.15",
         "symfony/security-core": "^5.3",
         "symfony/security-csrf": "^4.4|^5.0",
         "symfony/security-guard": "^5.3",
-        "symfony/security-http": "^5.3"
+        "symfony/security-http": "^5.3.2"
     },
     "require-dev": {
         "doctrine/annotations": "^1.10.4",
         "symfony/asset": "^4.4|^5.0",
         "symfony/browser-kit": "^4.4|^5.0",
diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
index 9416777b81a..098a2225c33 100644
--- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
@@ -93,11 +93,11 @@ class ContextListener extends AbstractListener
         }
 
         $request = $event->getRequest();
         $session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
 
-        $request->attributes->set('_security_firewall_run', true);
+        $request->attributes->set('_security_firewall_run', $this->sessionKey);
 
         if (null !== $session) {
             $usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : 0;
             $usageIndexReference = \PHP_INT_MIN;
             $sessionId = $request->cookies->all()[$session->getName()] ?? null;
@@ -167,11 +167,11 @@ class ContextListener extends AbstractListener
             return;
         }
 
         $request = $event->getRequest();
 
-        if (!$request->hasSession() || !$request->attributes->get('_security_firewall_run', false)) {
+        if (!$request->hasSession() || $request->attributes->get('_security_firewall_run') !== $this->sessionKey) {
             return;
         }
 
         if ($this->dispatcher) {
             $this->dispatcher->removeListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
index f995d215cab..bf9b028dcb6 100644
--- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
@@ -104,11 +104,11 @@ class ContextListenerTest extends TestCase
     public function testOnKernelResponseWithoutSession()
     {
         $tokenStorage = new TokenStorage();
         $tokenStorage->setToken(new UsernamePasswordToken('test1', 'pass1', 'phpunit'));
         $request = new Request();
-        $request->attributes->set('_security_firewall_run', true);
+        $request->attributes->set('_security_firewall_run', '_security_session');
         $session = new Session(new MockArraySessionStorage());
         $request->setSession($session);
 
         $event = new ResponseEvent(
             $this->createMock(HttpKernelInterface::class),
@@ -210,11 +210,11 @@ class ContextListenerTest extends TestCase
         $dispatcher = $this->createMock(EventDispatcherInterface::class);
 
         $listener = new ContextListener($tokenStorage, [], 'key123', null, $dispatcher);
 
         $request = new Request();
-        $request->attributes->set('_security_firewall_run', true);
+        $request->attributes->set('_security_firewall_run', '_security_key123');
         $request->setSession($session);
 
         $event = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new Response());
 
         $dispatcher->expects($this->once())
@@ -368,11 +368,11 @@ class ContextListenerTest extends TestCase
 
     protected function runSessionOnKernelResponse($newToken, $original = null)
     {
         $session = new Session(new MockArraySessionStorage());
         $request = new Request();
-        $request->attributes->set('_security_firewall_run', true);
+        $request->attributes->set('_security_firewall_run', '_security_session');
         $request->setSession($session);
         $requestStack = new RequestStack();
         $requestStack->push($request);
 
         if (null !== $original) {

Vulnerable code:         "symfony/security-http": "^5.3"
        $request->attributes->set('_security_firewall_run', true);
        if (!$request->hasSession() || !$request->attributes->get('_security_firewall_run', false)) {
        $request->attributes->set('_security_firewall_run', true);
        $request->attributes->set('_security_firewall_run', true);
        $request->attributes->set('_security_firewall_run', true);
Fixed code: 
    public function testMultipleFirewalls()
    {
        $client = $this->createClient(['test_case' => 'Authenticator', 'root_config' => 'multiple_firewalls.yml']);

        $client->request('POST', '/firewall1/login', [
            '_username' => 'jane@example.org',
            '_password' => 'test',
        ]);

        $client->request('GET', '/firewall2/profile');
        $this->assertResponseRedirects('http://localhost/login');
    }
imports:
- { resource: ./config.yml }
- { resource: ./security.yml }

security:
    enable_authenticator_manager: true
    firewalls:
        firewall1:
            pattern: /firewall1
            provider: in_memory
            form_login:
                check_path: /firewall1/login
        firewall2:
            pattern: /firewall2
            provider: in_memory2
            form_login:
                check_path: /firewall2/login

firewall1_login:
    path: /firewall1/login

firewall2_profile:
    path: /firewall2/profile
    defaults:
        _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ProfileController
        "symfony/security-http": "^5.3.2"
        $request->attributes->set('_security_firewall_run', $this->sessionKey);
        if (!$request->hasSession() || $request->attributes->get('_security_firewall_run') !== $this->sessionKey) {
        $request->attributes->set('_security_firewall_run', '_security_session');
        $request->attributes->set('_security_firewall_run', '_security_key123');
        $request->attributes->set('_security_firewall_run', '_security_session');