Password reset poisoning - PHP

Password reset poisoning - PHP

Need

Secure password reset functionality

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 PasswordReset for implementing password reset functionality
  • Usage of Mail for sending emails
  • Usage of PasswordResetMail for sending password reset emails

Description

Non compliant code

        public function sendPasswordResetLink(Request $request)
{
    $user = User::where('email', $request->email)->first();
    if (!$user) {
        return response()->json(['error' => 'User not found'], 404);
    }

    $token = str_random(60);
    PasswordReset::create([
        'email' => $request->email,
        'token' => $token,
    ]);

    $link = url('/password/reset/' . $token);
    Mail::to($request->email)->send(new PasswordResetMail($link));

    return response()->json(['message' => 'Password reset link sent']);
}
        
        

The above code is an example of a vulnerable password reset function in a Laravel application. The function sendPasswordResetLink() is used to generate a password reset link for the user who requests it.

The function first checks if the user with the given email exists in the database. If the user exists, it generates a random token and stores it along with the user's email in the PasswordReset table.

Then, it creates a password reset link by appending the token to the application's URL. The problem here is that the url() helper function in Laravel uses the APP_URL environment variable to generate the URL. If an attacker can manipulate the APP_URL to point to a domain under their control, they can receive the password reset links.

Finally, the function sends the password reset link to the user's email and returns a success message. If an attacker can poison the APP_URL, they can intercept the password reset link and potentially gain unauthorized access to the user's account.

Steps

  • Validate the email address in the request to ensure it belongs to a registered user.
  • Generate a secure, random token for the password reset process.
  • Store the token and the user's email in a secure way, such as in a database table specifically for password resets.
  • Create the password reset link using a secure method that doesn't allow for manipulation of the domain. The link should point to your application's domain and include the secure token as a parameter.
  • Send the password reset link to the user's email address using a secure method, such as a secure email service.
  • Ensure that the password reset process on the server side validates the token and the user's email address before allowing the password to be reset.

Compliant code

        public function sendPasswordResetLink(Request $request)
{
    $request->validate([
        'email' => 'required|email|exists:users,email',
    ]);

    $user = User::where('email', $request->email)->first();

    $token = hash('sha256', Str::random(60));

    DB::table('password_resets')->insert([
        'email' => $request->email,
        'token' => $token,
        'created_at' => Carbon::now()
    ]);

    $link = secure_url('password/reset', $token);

    Mail::to($request->email)->send(new PasswordResetMail($link));

    return response()->json(['message' => 'Password reset link sent']);
}
        
        

The updated code now includes several security improvements to mitigate the risk of password reset poisoning:

1. Email Validation: The email address from the request is validated to ensure it is in the correct format and exists in the users table in the database. This is done using Laravel's built-in validation rules in the $request->validate() method.

2. Secure Token Generation: A secure, random token is generated using Laravel's Str::random() method, which is then hashed using SHA-256 for additional security.

3. Secure Token Storage: The token and the user's email are stored in a 'password_resets' table in the database. This table should be secured and only accessible by trusted parts of the application.

4. Secure Link Generation: The password reset link is generated using Laravel's secure_url() function, which creates a URL using HTTPS. The token is included as a parameter in the URL.

5. Secure Email Sending: The password reset link is sent to the user's email address using Laravel's Mail::to() function. This should be configured to use a secure email service.

6. Server-Side Validation: The server-side code that handles the password reset request (not shown here) should validate the token and the user's email address before allowing the password to be reset. This ensures that only legitimate password reset requests are processed.

References