Skip to content

Disabled account is shown "Bad credentials" error #42793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fmonts opened this issue Aug 30, 2021 · 9 comments
Closed

Disabled account is shown "Bad credentials" error #42793

fmonts opened this issue Aug 30, 2021 · 9 comments

Comments

@fmonts
Copy link

fmonts commented Aug 30, 2021

I'm using the old 5.2 Security component, since we don't have docs for the new system yet.

I noticed that when User::isEnabled() returns false (for example if the user is banned or not yet approved), at login the user is shown "Bad credentials" error, because of this:

A DisabledException is thrown...

if (!$user->isEnabled()) {
$ex = new DisabledException('User account is disabled.');
$ex->setUser($user);
throw $ex;
}

Which extends AccountStatusException...

class DisabledException extends AccountStatusException

Which throws a BadCredentialsException

} catch (AccountStatusException | BadCredentialsException $e) {
if ($this->hideUserNotFoundExceptions && !$e instanceof CustomUserMessageAccountStatusException) {
throw new BadCredentialsException('Bad credentials.', 0, $e);
}

This was changed in May, here: https://symfony.com/blog/cve-2021-21424-prevent-user-enumeration-in-authentication-mechanisms

But I do not think this is a proper solution, if a user is banned or pending approval, and they insert the valid credentials, they shouldn't see an "Invalid credential" errors, this will make them think they forgot the password or their account has been stolen, and try to reset the password... (without success, since after the reset the same error will be shown, so they will end up contacting the admin in despair)

In my case I did this in login controller:

if(is_a($error, DisabledException::class) && $error->getUser()->isStatus(User::STATUS_PENDING_APPROVAL)) {
    // Show "please wait to be approved" message
}

Which is not working anymore.

A workaround is to set hide_user_not_found: false, but this will cause other unwanted effects (and as per the option name, it should regard "not found" users, not "disabled" ones)

@gnito-org
Copy link
Contributor

gnito-org commented Aug 30, 2021

Won't something like this work in your login controller?

try {
} catch (BadCredentialsException $e) {
  if ($e->getPrevious() instanceof AccountStatusException && $e->getPrevious()->getUser()->isStatus(User::STATUS_PENDING_APPROVAL)) {
     // Show "please wait to be approved" message
  } else {
    throw $e;
  }
}

@fmonts
Copy link
Author

fmonts commented Aug 31, 2021

Won't something like this work in your login controller?

try {
} catch (BadCredentialsException $e) {
  if ($e->getPrevious() instanceof AccountStatusException && $e->getPrevious()->getUser()->isStatus(User::STATUS_PENDING_APPROVAL)) {
     // Show "please wait to be approved" message
  } else {
    throw $e;
  }
}

It doesn't work, $error = $authenticationUtils->getLastAuthenticationError(); doesn't throw any exception.

Also there is another common use case, if the account is not active because they haven't verified their email address.

@herndlm
Copy link
Contributor

herndlm commented Aug 31, 2021

I think this should stay "safe" by default and not reveal any infos.

Does https://symfony.com/doc/current/reference/configuration/security.html#hide-user-not-found help?

Update: sorry, just saw you tried that. I still think changing this might be problematic

@henrikac
Copy link

henrikac commented Dec 9, 2021

@fmonts not sure if this is still an issue but I had a similar problem the other day where I wanted to customize the "Bad credentials" message. What I did to change the error message was to modify my LoginFormAuthenticator::onAuthenticationFailure method

public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
    if ($exception instanceof BadCredentialsException)
    {
        $exception = throw new CustomUserMessageAuthenticationException('Invalid email or password', [], 0, $exception);
    }

    return parent::onAuthenticationFailure($request, $exception);
}

In your situation, if I understand your issue correctly, you could do something similar but changing if ($exception instanceof BadCredentialsException) to if ($exception instanceof DisabledException).

This way you can keep your login route short and clean

#[Route("/login", name: "login", methods: ["GET", "POST"])]
public function login(AuthenticationUtils $authenticationUtils): Response
{
    $error = $authenticationUtils->getLastAuthenticationError();
    $lastUsername = $authenticationUtils->getLastUsername();

    return $this->render('security/login.html.twig', [
        'error' => $error,
        'last_username' => $lastUsername
    ]);
}

@carsonbot
Copy link

Hey, thanks for your report!
There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?

@carsonbot
Copy link

Could I get an answer? If I do not hear anything I will assume this issue is resolved or abandoned. Please get back to me <3

@carsonbot
Copy link

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

@antfarmer
Copy link

After upgrading to Symfony 5.4, I've run into this problem as well. If there is an account that is locked or inactive for one reason or another, the user gets the 'Invalid credentials' error message. I'm not sure if I have something setup incorrectly, but it is definitely not behaving the same way as it did in previous versions.

Looking at the code in AuthenticatorManager#handleAuthenticationFailure:271 it does seem like a bug or at least a feature with a misleading name: hideUserNotFoundExceptions.

Replacing that conditional block with this made it work as expected and as previously:

if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException 
		|| $authenticationException instanceof UserNotFoundException)) {
	$authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
}

I would expect such a feature to only hide exceptions when the username is not found, not including cases where the user is found, but is inactive or locked out in some way. This used to provide useful feedback for our clients so they would not waste time trying to reset their password. I guess some could argue it is better to obfuscate the error in all cases, but this seems a bit paranoid, and again the feature seems improperly named. I would propose to either rename this or better yet, separate this into two features: e.g. hideUserNotFoundExceptions and hideAllAuthExceptions.

For now, my workaround is to disable 'hide_user_not_found' (which is not the default setting), and replacing the error message in the login controller method like this:

$authEx = $authUtils->getLastAuthenticationError(true);
if ($authEx instanceof UserNotFoundException) {  // you may need to also check for UsernameNotFoundException
	$authEx = new BadCredentialsException('Bad credentials.', 0, $authEx);
}

It looks like this bit of logic is in many classes so I'm curious to understand others' thoughts on this and if it could be addressed somehow.

@fmonts
Copy link
Author

fmonts commented Feb 27, 2023

I agree, the best behavior to ensure both privacy and good user experience, should be:

  • If the password is wrong and the user is locked, show bad credentials error
  • If the password is correct, show account locked exception

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants