Data uniqueness not properly verified - Ruby

Data uniqueness not properly verified - Ruby

Need

Ensuring proper validation of data uniqueness

Context

  • Usage of Ruby 2.0+ for developing Ruby applications
  • Usage of Rails framework for building web applications

Description

Non compliant code

        class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private
    def user_params
      params.require(:user).permit(:name, :email)
    end
end
        
        

The above code is vulnerable because it does not validate the uniqueness of the user's email. When a new user is created with the create action in the UsersController, the application simply accepts the parameters :name and :email without checking if the provided email already exists in the database.

This could allow an attacker to create multiple accounts with the same email address. This can lead to issues like account takeover if one user is able to reset the password for all accounts associated with the same email.

The user_params method is a private method used to whitelist the parameters for mass assignment. This is a security feature in Rails to prevent an attacker from setting arbitrary attributes by manipulating the parameters. However, it does not validate the uniqueness of the email parameter, which is the vulnerability in this case.

Steps

  • Add a uniqueness validation to the User model for the email attribute.
  • Handle the uniqueness validation error in the UsersController's create action.
  • Display an appropriate error message to the user when the email is not unique.
  • Consider using a database-level constraint to enforce uniqueness for the email attribute.

Compliant code

        class User < ApplicationRecord
  validates :email, uniqueness: true
end

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      if @user.errors.details[:email].any? { |error| error[:error] == :taken }
        flash[:error] = "Email has already been taken"
      end
      render 'new'
    end
  end

  private
    def user_params
      params.require(:user).permit(:name, :email)
    end
end
        
        

The code above fixes the vulnerability by adding a uniqueness validation to the User model for the email attribute. This is done by adding validates :email, uniqueness: true to the User model. This line of code ensures that before a user is saved, the email attribute is checked for uniqueness. If the email is not unique, the user will not be saved.

In the UsersController's create action, we handle the uniqueness validation error. If the user fails to save due to a non-unique email, the @user.errors.details[:email] array will contain a hash with :error key set to :taken. We check for this specific error and if it's present, we set a flash message to inform the user that the email has already been taken.

Finally, we render the 'new' view again, which should display the flash message to the user. This provides feedback to the user that the email they entered is not unique and they need to enter a different email.

As an additional step, consider adding a database-level constraint to enforce uniqueness for the email attribute. This will provide an extra layer of security by preventing duplicate emails from being saved even in the event of a race condition, where two users submit the same email at the same time.

References