Skip to main content

Password reset poisoning

Need

Prevention of unauthorized access through manipulated password reset links

Context

  • Usage of Ruby for building dynamic and object-oriented applications
  • Usage of Ruby on Rails for building web applications
  • Usage of ActiveRecord for Object-Relational Mapping (ORM) in database operations
  • Usage of ActionMailer for sending emails in a web application

Description

Non compliant code

class PasswordsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user
user.send_password_reset(params[:host])
else
redirect_to new_password_path, alert: "Invalid email address"
end
end
end

class User < ActiveRecord::Base
def send_password_reset(host)
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self, host).deliver
end
end

class UserMailer < ActionMailer::Base
def password_reset(user, host)
@user = user
mail to: user.email, subject: "Password Reset", from: "no-reply@#{host}"
end
end

In the above code, the PasswordsController receives an email and a host as parameters. If a user with the provided email exists, it triggers the send_password_reset method in the User model. This method generates a password reset token, sets the time the password reset was sent, saves the user, and then sends an email to the user with the password reset link.

The UserMailer is responsible for sending the email. It sets the user and sends the email to the user's email address with a subject of "Password Reset" and a from address of "no-reply@#{host}".

The vulnerability lies in the fact that the host parameter can be manipulated by an attacker. Since the host parameter is used to generate the from address in the password reset email, an attacker can change the host to a domain under his control. This could lead to a phishing attack where the attacker sends a password reset email from a domain that looks similar to the legitimate domain, tricks the user into clicking the link and entering their new password on the attacker's site.

Steps

  • Sanitize and validate the 'host' parameter in the 'send_password_reset' method of the User model to ensure it is a valid domain.
  • Implement a whitelist of allowed domains to prevent any unauthorized domain from being used in the password reset email.
  • Use a secure method to generate the password reset token, such as using a cryptographically secure random number generator.
  • Store the password reset token securely, such as using a hashed version of the token in the database.
  • Add a time limit for the validity of the password reset token to prevent it from being used indefinitely.
  • Use HTTPS for all password reset links to ensure the communication is encrypted and secure.
  • Implement rate limiting or CAPTCHA to prevent abuse of the password reset functionality.
  • Regularly update and patch the Ruby on Rails framework and its dependencies to ensure any security vulnerabilities are addressed.

Compliant code

class PasswordsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user
user.send_password_reset(request.host)
else
redirect_to new_password_path, alert: "Invalid email address"
end
end
end

class User < ActiveRecord::Base
def send_password_reset(host)
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self, host).deliver if valid_host?(host)
end

private

def valid_host?(host)
# Add your domain whitelist here
whitelist = ['yourdomain.com', 'anotherdomain.com']
whitelist.include?(host)
end
end

class UserMailer < ActionMailer::Base
def password_reset(user, host)
@user = user
mail to: user.email, subject: "Password Reset", from: "no-reply@#{host}"
end
end

The updated code includes a method valid_host? in the User model that checks if the provided host is included in a predefined whitelist of domains. This whitelist should include all domains that are authorized to send password reset emails. This way, even if an attacker tries to manipulate the host parameter, the password reset email will not be sent unless the host is included in the whitelist.

In the PasswordsController, the request.host is passed to the send_password_reset method instead of params[:host]. This ensures that the host is the one making the request, not a potentially manipulated host from the parameters.

These changes help to mitigate the risk of password reset poisoning by ensuring that only authorized domains can be used to send password reset emails. It's also recommended to use HTTPS for all password reset links, implement rate limiting or CAPTCHA to prevent abuse of the password reset functionality, and regularly update and patch the Ruby on Rails framework and its dependencies to address any security vulnerabilities.

References