SEC-2056: DaoAuthenticationProvider performs isPasswordValid when user not found

Previously authenticating a user could take significantly longer than
determining that a user does not exist. This was due to the fact that only
users that were found would use the password encoder and comparing a
password can take a significant amount of time. The difference in the
time required could allow a side channel attack that reveals if a user
exists.

The code has been updated to do comparison against a dummy password
even when the the user was not found.
This commit is contained in:
Rob Winch
2012-09-21 14:02:13 -05:00
parent f3b143f677
commit c076f0f2e1
2 changed files with 154 additions and 4 deletions

View File

@@ -31,19 +31,39 @@ import org.springframework.util.Assert;
* An {@link AuthenticationProvider} implementation that retrieves user details from a {@link UserDetailsService}.
*
* @author Ben Alex
* @author Rob Winch
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//~ Static fields/initializers =====================================================================================
/**
* The plaintext password used to perform {@link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
//~ Instance fields ================================================================================================
private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
private PasswordEncoder passwordEncoder;
/**
* The password used to perform {@link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
* not found to avoid SEC-2056. This is necessary, because some {@link PasswordEncoder} implementations will short circuit if the
* password is not in a valid format.
*/
private String userNotFoundEncodedPassword;
private SaltSource saltSource;
private UserDetailsService userDetailsService;
public DaoAuthenticationProvider() {
setPasswordEncoder(new PlaintextPasswordEncoder());
}
//~ Methods ========================================================================================================
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
@@ -80,6 +100,10 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
if(authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword, presentedPassword, null);
}
throw notFound;
} catch (Exception repositoryProblem) {
throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
@@ -106,14 +130,14 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
if (passwordEncoder instanceof PasswordEncoder) {
this.passwordEncoder = (PasswordEncoder) passwordEncoder;
setPasswordEncoder((PasswordEncoder) passwordEncoder);
return;
}
if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
final org.springframework.security.crypto.password.PasswordEncoder delegate =
(org.springframework.security.crypto.password.PasswordEncoder)passwordEncoder;
this.passwordEncoder = new PasswordEncoder() {
setPasswordEncoder(new PasswordEncoder() {
public String encodePassword(String rawPass, Object salt) {
checkSalt(salt);
return delegate.encode(rawPass);
@@ -127,7 +151,7 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
private void checkSalt(Object salt) {
Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder");
}
};
});
return;
}
@@ -135,6 +159,13 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
throw new IllegalArgumentException("passwordEncoder must be a PasswordEncoder instance");
}
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.userNotFoundEncodedPassword = passwordEncoder.encodePassword(USER_NOT_FOUND_PASSWORD, null);
this.passwordEncoder = passwordEncoder;
}
protected PasswordEncoder getPasswordEncoder() {
return passwordEncoder;
}