Skip to main content

Account Takeover

Need

To prevent unauthorized access and control over a user account.

Context

  • Usage of Elixir 1.12 for functional programming and building scalable applications
  • Usage of Plug for building composable web applications
  • 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 implements a password reset functionality. However, it does not validate whether the requester is indeed the owner of the account. This allows an attacker to request a password reset for any account and receive the reset link, allowing them to takeover that account.

Steps

  • Implement verification mechanisms to ensure that the person requesting a password reset is indeed the owner of the account.
  • This could be done by sending a verification code to the user's registered email before proceeding with the password reset process.
  • Ensure that the password reset URL is only sent to the user's registered email and is not exposed anywhere else.

Compliant code

defmodule MyAppWeb.PasswordResetController do
use MyAppWeb, :controller

def create(conn, %{'email' => email}) do
user = Accounts.get_user_by_email(email)
verification_code = Accounts.generate_verification_code(user)
MyApp.Mailer.deliver_verification_code(user.email, verification_code)
send_resp(conn, :ok, "")
end

def update(conn, %{'email' => email, 'verification_code' => verification_code, 'new_password' => new_password}) do
user = Accounts.get_user_by_email(email)
if Accounts.verify(user, verification_code) do
Accounts.reset_password(user, new_password)
send_resp(conn, :ok, "")
else
send_resp(conn, :bad_request, "")
end
end
end

The code now first sends a verification code to the user's registered email when a password reset is requested. The password reset process only proceeds if the correct verification code is provided, thereby validating that the requester is indeed the owner of the account.

References