CVE-2021-45890 (published 2021-12-27T20:15Z)

basic/BasicAuthProvider.java in AuthGuard before 0.9.0 allows authentication via an inactive identifier.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/AuthGuard/AuthGuard
commit_details: repo_url: https://github.com/AuthGuard/AuthGuard
author: Khaled Y.M
author_date: 2021-10-05 21:53:55+01:00
msg: Prevent authentication with inactive identifiers
repository: repo_url: https://github.com/AuthGuard/AuthGuard
repo_name: AuthGuard/AuthGuard
description: Simple and extensible identity management service
repo_language: Java
owner: AuthGuard
file_changes: filename: BasicAuthProvider.java
change_type: ModificationType.MODIFY
diff: @@ -7,10 +7,7 @@ import com.nexblocks.authguard.service.CredentialsService;
 import com.nexblocks.authguard.service.exceptions.ServiceAuthorizationException;
 import com.nexblocks.authguard.service.exceptions.ServiceException;
 import com.nexblocks.authguard.service.exceptions.codes.ErrorCode;
-import com.nexblocks.authguard.service.model.AccountBO;
-import com.nexblocks.authguard.service.model.AuthRequestBO;
-import com.nexblocks.authguard.service.model.CredentialsBO;
-import com.nexblocks.authguard.service.model.EntityType;
+import com.nexblocks.authguard.service.model.*;
 import com.google.inject.Inject;
 import io.vavr.control.Either;
 import org.slf4j.Logger;
@@ -86,7 +83,14 @@ public class BasicAuthProvider {
     private Either<Exception, AccountBO> verifyCredentialsAndGetAccount(final String username, final String password) {
         final Optional<CredentialsBO> credentials = credentialsService.getByUsernameUnsafe(username);
 
+        // TODO replace this with Either mapping
         if (credentials.isPresent()) {
+            final Optional<Exception> validationError = checkIdentifier(credentials.get(), username);
+
+            if (validationError.isPresent()) {
+                return Either.left(validationError.get());
+            }
+
             if (securePassword.verify(password, credentials.get().getHashedPassword())) {
                 return getAccountById(credentials.get().getAccountId());
             } else {
@@ -103,6 +107,12 @@ public class BasicAuthProvider {
         final Optional<CredentialsBO> credentials = credentialsService.getByUsernameUnsafe(username);
 
         if (credentials.isPresent()) {
+            final Optional<Exception> validationError = checkIdentifier(credentials.get(), username);
+
+            if (validationError.isPresent()) {
+                return Either.left(validationError.get());
+            }
+
             return getAccountById(credentials.get().getAccountId());
         } else {
             return Either.left(new ServiceAuthorizationException(ErrorCode.CREDENTIALS_DOES_NOT_EXIST,
@@ -110,6 +120,25 @@ public class BasicAuthProvider {
         }
     }
 
+    private Optional<Exception> checkIdentifier(final CredentialsBO credentials,
+                                                final String identifier) {
+        final Optional<UserIdentifierBO> matchedIdentifier = credentials.getIdentifiers()
+                .stream()
+                .filter(existing -> identifier.equals(existing.getIdentifier()))
+                .findFirst();
+
+        if (matchedIdentifier.isEmpty()) {
+            return Optional.of(new IllegalStateException("No identifier matched but credentials were returned"));
+        }
+
+        if (!matchedIdentifier.get().isActive()) {
+            return Optional.of(new ServiceAuthorizationException(ErrorCode.INACTIVE_IDENTIFIER,
+                    "Identifier is not active", EntityType.ACCOUNT, credentials.getAccountId()));
+        }
+
+        return Optional.empty();
+    }
+
     private Either<Exception, AccountBO> getAccountById(final String accountId) {
         final Optional<AccountBO> account = accountsService.getById(accountId);
 

programming_language: Java
filename: BasicAuthProviderTest.java
change_type: ModificationType.MODIFY
diff: @@ -64,6 +64,7 @@ class BasicAuthProviderTest {
                 .withIdentifiers(UserIdentifierBO.builder()
                         .identifier(username)
                         .type(UserIdentifier.Type.USERNAME)
+                        .active(true)
                         .build());
         final HashedPasswordBO hashedPasswordBO = HashedPasswordBO.builder()
                 .password(credentials.getHashedPassword().getPassword())
@@ -79,6 +80,27 @@ class BasicAuthProviderTest {
         assertThat(result.get()).isEqualTo(account);
     }
 
+    @Test
+    void authenticateInactiveIdentifier() {
+        final String username = "username";
+        final String password = "password";
+        final String authorization = Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
+
+        final CredentialsBO credentials = RANDOM.nextObject(CredentialsBO.class)
+                .withIdentifiers(UserIdentifierBO.builder()
+                        .identifier(username)
+                        .type(UserIdentifier.Type.USERNAME)
+                        .active(false)
+                        .build());
+
+        Mockito.when(credentialsService.getByUsernameUnsafe(username)).thenReturn(Optional.of(credentials));
+
+        final Either<Exception, AccountBO> result = basicAuth.authenticateAndGetAccount(authorization);
+
+        assertThat(result.isLeft()).isTrue();
+        assertThat(result.getLeft()).isInstanceOf(ServiceAuthorizationException.class);
+    }
+
     @Test
     void authenticateNotFound() {
         final String username = "username";

programming_language: Java
filename: ErrorCode.java
change_type: ModificationType.MODIFY
diff: @@ -33,6 +33,7 @@ public enum ErrorCode {
     TOKEN_EXPIRED_OR_DOES_NOT_EXIST("AT.025"),
     TOKEN_GENERATION_FAILED("AT.031"),
     ACCOUNT_IS_LOCKED("AT.032"),
+    INACTIVE_IDENTIFIER("AT.033"),
     GENERIC_AUTH_FAILURE("AT.039"),
 
     UNSUPPORTED_JWT_ALGORITHM("JT.021"),

programming_language: Rust
filename: UserIdentifier.java
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,11 @@ public interface UserIdentifier {
     Long getId(); // only useful for relational DBs
     Type getType();
     String getIdentifier();
-    boolean isActive();
+
+    @Value.Default
+    default boolean isActive() {
+        return true;
+    }
 
     enum Type {
         USERNAME,

programming_language: Java