Password Reset Poisoning - Elixir

Password Reset Poisoning - Elixir

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