Skip to main content

User enumeration

Need

Prevention of user enumeration

Context

  • Usage of PHP 7.0 for server-side scripting and web development
  • Usage of Request for making HTTP requests
  • Usage of the User dependency for managing user-related functionality
  • Usage of Hash for secure password storage and authentication

Description

Non compliant code

public function login(Request $request)
{
$user = User::where('email', $request->email)->first();

if ($user) {
if (Hash::check($request->password, $user->password)) {
// Authenticated successfully
return response()->json(['message' => 'Authenticated successfully']);
} else {
// Invalid password
return response()->json(['message' => 'Invalid password']);
}
} else {
// User not found
return response()->json(['message' => 'User not found']);
}
}

In the above code, the system is handling user login by first checking if the user with the provided email exists in the database. If the user exists, it then checks if the provided password is correct.

The vulnerability lies in the fact that the system provides different responses for existent and non-existent users. If a user is found, it returns either "Authenticated successfully" or "Invalid password" depending on whether the password is correct. If the user is not found, it returns "User not found".

This allows an attacker to enumerate valid users via error messages. For example, by entering random email addresses, an attacker can determine which ones are associated with valid users based on whether the system returns "Invalid password" or "User not found". This can lead to user enumeration attacks where an attacker can gather a list of valid users for further attacks.

Steps

  • First, modify the login function to not return different messages based on whether the user exists or not. This can be done by returning a generic error message such as 'Invalid credentials' regardless of whether the user was found or the password was incorrect.
  • Second, consider implementing a delay or limit on login attempts to prevent brute force attacks. This can be done by using a rate limiting middleware or by implementing a custom solution.
  • Third, ensure that the response times are consistent regardless of whether the user exists or not. This can be achieved by using constant time comparison functions when checking the password.

Compliant code

public function login(Request $request)
{
$user = User::where('email', $request->email)->first();

if ($user && Hash::check($request->password, $user->password)) {
// Authenticated successfully
return response()->json(['message' => 'Authenticated successfully']);
} else {
// Invalid credentials
return response()->json(['message' => 'Invalid credentials'], 401);
}
}

The updated code no longer provides different responses based on whether the user exists or not. Instead, it returns a generic 'Invalid credentials' message for both non-existent users and incorrect passwords. This prevents an attacker from being able to enumerate valid users via error messages.

In the updated code, the User::where('email', $request->email)->first(); line attempts to find a user with the provided email. If a user is found and the provided password matches the user's password (Hash::check($request->password, $user->password)), the function returns a success message.

If the user is not found or the password does not match, the function returns a 'Invalid credentials' message. This is done regardless of whether the user was found or not, preventing user enumeration.

Please note that this code does not implement a delay or limit on login attempts, which would help prevent brute force attacks. This could be done using a rate limiting middleware or a custom solution. Also, to ensure that response times are consistent, consider using constant time comparison functions when checking the password.

References