Skip to main content

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.

References