Przeglądaj źródła

Merge commit from fork

* fix(core): Prevent timing attack user enumeration in authentication

Perform a dummy bcrypt hash check when a user is not found during
authentication. This ensures consistent response times regardless of
whether the username exists, mitigating timing-based user enumeration
attacks.

Fixes GHSA-6f65-4fv2-wwch

* fix(core): Use valid bcrypt hash for dummy password check
Michael Bromley 2 dni temu
rodzic
commit
7f0c5556ec

+ 11 - 0
packages/core/src/config/auth/native-authentication-strategy.ts

@@ -17,6 +17,14 @@ export interface NativeAuthenticationData {
 
 
 export const NATIVE_AUTH_STRATEGY_NAME = 'native';
 export const NATIVE_AUTH_STRATEGY_NAME = 'native';
 
 
+/**
+ * A pre-computed bcrypt hash used for dummy password checks to prevent timing attacks.
+ * When a user is not found, we still perform a hash comparison against this dummy value
+ * to ensure consistent response times regardless of whether the user exists.
+ * This mitigates user enumeration via timing analysis.
+ */
+const DUMMY_PASSWORD_HASH = '$2b$12$SFfIOqrqph9N4yvWLtbqteiV5C6GEN/YOumGLryDDbHeMLtSQo4/6';
+
 /**
 /**
  * @description
  * @description
  * This strategy implements a username/password credential-based authentication, with the credentials
  * This strategy implements a username/password credential-based authentication, with the credentials
@@ -53,6 +61,9 @@ export class NativeAuthenticationStrategy implements AuthenticationStrategy<Nati
     async authenticate(ctx: RequestContext, data: NativeAuthenticationData): Promise<User | false> {
     async authenticate(ctx: RequestContext, data: NativeAuthenticationData): Promise<User | false> {
         const user = await this.userService.getUserByEmailAddress(ctx, data.username);
         const user = await this.userService.getUserByEmailAddress(ctx, data.username);
         if (!user) {
         if (!user) {
+            // Perform a dummy password check to prevent timing attacks that could
+            // be used to determine whether a user account exists.
+            await this.passwordCipher.check(data.password, DUMMY_PASSWORD_HASH);
             return false;
             return false;
         }
         }
         const passwordMatch = await this.verifyUserPassword(ctx, user.id, data.password);
         const passwordMatch = await this.verifyUserPassword(ctx, user.id, data.password);