Password Reset Poisoning
Need
To ensure secure password resets and prevent attackers from gaining control over user accounts.
Context
- Usage of Elixir 1.12 for functional programming and building scalable applications
- Usage of Plug for building composable web applications in Elixir
- Usage of Phoenix web framework for building web applications
Description
Non compliant code
defmodule MyAppWeb.PasswordResetController do
use MyAppWeb, :controller
def create(conn, %{'email' => email}) do
user = Accounts.get_user_by_email(email)
Accounts.deliver_password_reset_instructions(user, conn)
send_resp(conn, :ok, "")
end
end
defmodule MyApp.Accounts do
def deliver_password_reset_instructions(%User{} = user, conn) do
url = MyAppWeb.Router.Helpers.password_reset_url(conn, :edit, user.reset_password_token)
MyApp.Mailer.deliver_password_reset_instructions(user.email, url)
end
end
The code generates a password reset link using the host provided in the request headers. An attacker can manipulate the request headers to provide a host that they control, resulting in the application generating a reset link that points to the attacker's host.
Steps
- Ensure the password reset URL is generated using a trusted host value.
- Don't rely on values provided in the request headers for generating the reset URL.
- Add a configuration for the application's host and use that when generating the password reset URL.
Compliant code
defmodule MyAppWeb.PasswordResetController do
use MyAppWeb, :controller
def create(conn, %{'email' => email}) do
user = Accounts.get_user_by_email(email)
Accounts.deliver_password_reset_instructions(user)
send_resp(conn, :ok, "")
end
end
defmodule MyApp.Accounts do
def deliver_password_reset_instructions(%User{} = user) do
host = Application.get_env(:my_app, MyAppWeb.Endpoint)[:url][:host]
url = MyAppWeb.Router.Helpers.password_reset_url(MyAppWeb.Endpoint, :edit, user.reset_password_token, host: host)
MyApp.Mailer.deliver_password_reset_instructions(user.email, url)
end
end
The code now generates the password reset URL using a trusted host value from the application's configuration, instead of the host provided in the request headers. This prevents an attacker from manipulating the reset URL to point to their own host.