Account Takeover - Elixir
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
Insecure Code Example
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.
Secure Code Example
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
Last updated
2023/09/18