Authentication mechanisms should not reveal whether a username exists in the system.
User enumeration occurs when an application discloses whether a given username exists in its database, for example through sign-in, sign-on, or forgot-password functionalities. When error messages or exception handling differ depending on whether a username is valid, attackers can use brute-force techniques to harvest valid usernames. This facilitates further attacks such as credential stuffing, phishing, and targeted password guessing, and impacts the privacy of the affected users.
If an attacker can enumerate valid usernames, it significantly increases the success rate of credential stuffing or social engineering. They can launch targeted credential-stuffing or password-guessing campaigns against confirmed accounts, and use the harvested usernames in phishing schemes. This also degrades user privacy, since the mere existence of an account can reveal personal information.
The following code leaks information about the existence of usernames by using distinct error messages, throwing
UsernameNotFoundException outside the loadUserByUsername method, or disabling HideUserNotFoundExceptions.
public String authenticate(String username, String password) {
MyUserDetailsService s1 = new MyUserDetailsService();
MyUserPrincipal u1 = s1.loadUserByUsername(username);
if(u1 == null) {
throw new BadCredentialsException(username+" doesn't exist in our database"); // Noncompliant
}
}
public String authenticate2(String username, String password) {
MyUserDetailsService s2 = new MyUserDetailsService();
MyUserPrincipal user = s2.loadUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("user not found"); // Noncompliant
}
}
public void configure() {
DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
daoauth.setUserDetailsService(new MyUserDetailsService());
daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
daoauth.setHideUserNotFoundExceptions(false); // Noncompliant
builder.authenticationProvider(daoauth);
}
public boolean authenticate(String username, String password) throws AuthenticationException {
verifyCredentials(username, password);
return true;
}
private void verifyCredentials(String username, String password) throws AuthenticationException {
Details user = null;
try {
user = loadUserByUsername(username);
} catch (UsernameNotFoundException | DataAccessException e) {
// Hide the reason to avoid disclosing user existence.
}
if (user == null || !user.isPasswordCorrect(password)) {
throw new BadCredentialsException("Bad credentials");
}
}
public void configure() {
DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
daoauth.setUserDetailsService(new MyUserDetailsService());
daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
daoauth.setHideUserNotFoundExceptions(true);
builder.authenticationProvider(daoauth);
}