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.