Email uniqueness not properly verified - PHP

Email uniqueness not properly verified - PHP

Need

Enforce email uniqueness verification during registration process

Context

  • Usage of PHP 7.0 for server-side scripting and web development
  • Usage of Request for making HTTP requests
  • Usage of Validator for data validation and sanitization
  • Usage of the User dependency for user-related functionality
  • Usage of Hash for generating and validating hash values

Description

Non compliant code

        public function register(Request $request)
{
    $validator = Validator::make($request->all(), [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255',
        'password' => 'required|string|min:8',
    ]);

    if ($validator->fails()) {
        return response()->json($validator->errors()->toJson(), 400);
    }

    $user = User::create([
        'name' => $request->get('name'),
        'email' => $request->get('email'),
        'password' => Hash::make($request->get('password')),
    ]);

    return response()->json(compact('user'),201);
}
        
        

In the above code, the register function is used to register a new user. This function first validates the request data using Laravel's built-in validation. It checks if the 'name', 'email', and 'password' fields are present and meet certain criteria.

The 'email' field is validated as a string, having a maximum length of 255, and as an email. However, Laravel's email validation allows the plus character (+) in the local part of the email address, as it is valid according to the email format specification (RFC 5322).

This means that an attacker can register multiple accounts with the same email address by appending a plus sign and any string to the local part of the email. For example, 'attacker@example.com', 'attacker+1@example.com', 'attacker+2@example.com', and so on, will all be treated as unique by the system but will actually deliver emails to the same address, 'attacker@example.com'.

After validation, the User::create method is used to create a new user with the validated data. The password is hashed before storing it in the database. The newly created user is then returned in the response.

The vulnerability lies in the fact that the system does not properly verify the uniqueness of the email address, allowing an attacker to create multiple accounts with the same email address.

Steps

  • Add a unique constraint to the 'email' field in the database schema to prevent duplicate entries.
  • Before creating a new user, check if the email already exists in the database. If it does, return an error message.
  • Normalize the email address before storing it in the database. This could include converting the email to lowercase and removing any plus signs and the characters following them before the '@' symbol.
  • Consider using a library or built-in function to validate and normalize email addresses.

Compliant code

        public function register(Request $request)
{
    $validator = Validator::make($request->all(), [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => 'required|string|min:8',
    ]);

    if ($validator->fails()) {
        return response()->json($validator->errors()->toJson(), 400);
    }

    $email = $request->get('email');
    $email = $this->normalizeEmail($email);

    $user = User::create([
        'name' => $request->get('name'),
        'email' => $email,
        'password' => Hash::make($request->get('password')),
    ]);

    return response()->json(compact('user'),201);
}

private function normalizeEmail($email)
{
    $email = strtolower($email);
    $emailParts = explode('@', $email);
    $emailParts[0] = explode('+', $emailParts[0])[0];
    return implode('@', $emailParts);
}
        
        

The updated code includes a unique constraint on the 'email' field in the validation rules. This ensures that the email address is not only a valid email but also unique in the 'users' table.

The email address is normalized before it is stored in the database. The normalizeEmail function converts the email address to lowercase and removes any plus signs and the characters following them before the '@' symbol. This is done by splitting the email address into two parts at the '@' symbol, then splitting the first part at the '+' symbol and only keeping the part before the '+'. The parts are then joined back together to form the normalized email address.

This normalization process ensures that email addresses like 'example+1@test.com' and 'example+2@test.com' are treated as the same as 'example@test.com', preventing an attacker from creating multiple accounts with the same email address.

References