diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md
index 23e1671360b2c..00f1df4aafe07 100644
--- a/CHANGELOG-3.4.md
+++ b/CHANGELOG-3.4.md
@@ -7,6 +7,10 @@ in 3.4 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1
+* 3.4.48 (2021-05-12)
+
+ * security #cve-2021-21424 [Security][Guard] Prevent user enumeration (chalasr)
+
* 3.4.47 (2020-11-27)
* bug #38628 [DoctrineBridge] indexBy could reference to association columns (juanmiguelbesada)
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml
index 43321494e0194..9e27dcb575f49 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml
@@ -17,7 +17,7 @@
-
+
@@ -41,6 +41,7 @@
+ %security.authentication.hide_user_not_found%
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 280401e919033..63c96abd3a29f 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private $requestStackSize = 0;
private $resetServices = false;
- const VERSION = '3.4.47';
- const VERSION_ID = 30447;
+ const VERSION = '3.4.48';
+ const VERSION_ID = 30448;
const MAJOR_VERSION = 3;
const MINOR_VERSION = 4;
- const RELEASE_VERSION = 47;
+ const RELEASE_VERSION = 48;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '11/2020';
diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php
index 172556ac2868b..9557fa00047c1 100644
--- a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php
+++ b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php
@@ -13,6 +13,7 @@
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
@@ -83,7 +84,7 @@ public function authenticate(TokenInterface $token)
$this->userChecker->checkPreAuth($user);
$this->checkAuthentication($user, $token);
$this->userChecker->checkPostAuth($user);
- } catch (BadCredentialsException $e) {
+ } catch (AccountStatusException $e) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials.', 0, $e);
}
diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php
index 7b984e304d814..c20b6ca2eaa1d 100644
--- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php
@@ -79,7 +79,7 @@ public function testAuthenticateWhenProviderDoesNotReturnAnUserInterface()
public function testAuthenticateWhenPreChecksFails()
{
- $this->expectException('Symfony\Component\Security\Core\Exception\CredentialsExpiredException');
+ $this->expectException(BadCredentialsException::class);
$userChecker = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserCheckerInterface')->getMock();
$userChecker->expects($this->once())
->method('checkPreAuth')
@@ -97,7 +97,7 @@ public function testAuthenticateWhenPreChecksFails()
public function testAuthenticateWhenPostChecksFails()
{
- $this->expectException('Symfony\Component\Security\Core\Exception\AccountExpiredException');
+ $this->expectException(BadCredentialsException::class);
$userChecker = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserCheckerInterface')->getMock();
$userChecker->expects($this->once())
->method('checkPostAuth')
@@ -116,7 +116,7 @@ public function testAuthenticateWhenPostChecksFails()
public function testAuthenticateWhenPostCheckAuthenticationFails()
{
$this->expectException('Symfony\Component\Security\Core\Exception\BadCredentialsException');
- $this->expectExceptionMessage('Bad credentials');
+ $this->expectExceptionMessage('Bad credentials.');
$provider = $this->getProvider();
$provider->expects($this->once())
->method('retrieveUser')
@@ -124,7 +124,7 @@ public function testAuthenticateWhenPostCheckAuthenticationFails()
;
$provider->expects($this->once())
->method('checkAuthentication')
- ->willThrowException(new BadCredentialsException())
+ ->willThrowException(new CredentialsExpiredException())
;
$provider->authenticate($this->getSupportedToken());
diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php
index 11bda3cd180da..190290c46afb3 100644
--- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php
+++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php
@@ -17,7 +17,10 @@
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
@@ -40,6 +43,7 @@ class GuardAuthenticationListener implements ListenerInterface
private $guardAuthenticators;
private $logger;
private $rememberMeServices;
+ private $hideUserNotFoundExceptions;
/**
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
@@ -48,7 +52,7 @@ class GuardAuthenticationListener implements ListenerInterface
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
* @param LoggerInterface $logger A LoggerInterface instance
*/
- public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, $guardAuthenticators, LoggerInterface $logger = null)
+ public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, $guardAuthenticators, LoggerInterface $logger = null, $hideUserNotFoundExceptions = true)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
@@ -59,6 +63,7 @@ public function __construct(GuardAuthenticatorHandler $guardHandler, Authenticat
$this->providerKey = $providerKey;
$this->guardAuthenticators = $guardAuthenticators;
$this->logger = $logger;
+ $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
}
/**
@@ -163,6 +168,12 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn
$this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
}
+ // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
+ // to prevent user enumeration via response content
+ if ($this->hideUserNotFoundExceptions && ($e instanceof UsernameNotFoundException || $e instanceof AccountStatusException)) {
+ $e = new BadCredentialsException('Bad credentials.', 0, $e);
+ }
+
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
if ($response instanceof Response) {
diff --git a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php
index 6572696d48fca..a2e4671d52fff 100644
--- a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php
+++ b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php
@@ -16,6 +16,9 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\LockedException;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
@@ -208,6 +211,54 @@ public function testHandleCatchesAuthenticationException()
$listener->handle($this->event);
}
+ /**
+ * @dataProvider exceptionsToHide
+ */
+ public function testHandleHidesInvalidUserExceptions(AuthenticationException $exceptionToHide)
+ {
+ $authenticator = $this->createMock(AuthenticatorInterface::class);
+ $providerKey = 'my_firewall2';
+
+ $authenticator
+ ->expects($this->once())
+ ->method('supports')
+ ->willReturn(true);
+ $authenticator
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->willReturn(['username' => 'robin', 'password' => 'hood']);
+
+ $this->authenticationManager
+ ->expects($this->once())
+ ->method('authenticate')
+ ->willThrowException($exceptionToHide);
+
+ $this->guardAuthenticatorHandler
+ ->expects($this->once())
+ ->method('handleAuthenticationFailure')
+ ->with($this->callback(function ($e) use ($exceptionToHide) {
+ return $e instanceof BadCredentialsException && $exceptionToHide === $e->getPrevious();
+ }), $this->request, $authenticator, $providerKey);
+
+ $listener = new GuardAuthenticationListener(
+ $this->guardAuthenticatorHandler,
+ $this->authenticationManager,
+ $providerKey,
+ [$authenticator],
+ $this->logger
+ );
+
+ $listener->handle($this->event);
+ }
+
+ public function exceptionsToHide()
+ {
+ return [
+ [new UsernameNotFoundException()],
+ [new LockedException()],
+ ];
+ }
+
/**
* @group legacy
*/
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy