Brute Force Attack in Phoenix with Dynamodb
Brute Force Attack in Phoenix with Dynamodb — how this specific combination creates or exposes the vulnerability
A brute force attack against a Phoenix application using DynamoDB as a backend typically targets authentication or session endpoints where usernames or identifiers are validated by querying DynamoDB. Because DynamoDB is a NoSQL database, the pattern of requests—many repeated queries for similar keys—can be observable through application logs or monitoring, and each query may reveal whether a user exists based on whether DynamoDB returns an item or an empty response. When Phoenix controllers call DynamoDB via the AWS SDK without protective controls, the absence of rate limiting or account lockout allows an attacker to iterate over possible usernames or IDs and infer valid accounts. This becomes especially relevant when endpoints use sequential or guessable identifiers (for example, numeric user IDs or email-based usernames) and do not enforce uniform response times or generic error messages.
Consider a Phoenix controller that retrieves a user by email before password verification:
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyApp.Dynamo
def show(conn, %{"email" => email}) do
case Dynamo.get_user_by_email(email) do
{:ok, nil} -
-> conn |> put_status(:not_found) |> json(%{error: "not found"})
{:ok, user} -
-> json(conn, UserView.render("user.json", user: user))
end
end
end
If DynamoDB does not enforce strict rate limiting at the API level, an attacker can send many requests with different email values. Because the endpoint returns different HTTP status codes (404 versus 200), the attacker learns which emails exist. This information can be combined with other attacks such as credential stuffing if the same emails are used for authentication. The DynamoDB table’s partition key design can also amplify risk: if queries are routed to a single partition due to a poorly chosen key, the increased latency and error patterns may further expose behavior to an observant attacker. MiddleBrick scans can detect such authentication endpoints and missing controls, highlighting the need for mitigations like constant-time checks and rate limiting.
Dynamodb-Specific Remediation in Phoenix — concrete code fixes
To reduce the risk of brute forcing, ensure that authentication paths do not leak user existence through timing differences or status codes. Implement a constant-time lookup for user existence and apply rate limiting at the Phoenix pipeline. Below is a hardened controller example that uses a fixed query regardless of input and adds plug-based rate limiting.
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyApp.Dynamo
import Plug.Conn
# Rate limit by IP for authentication endpoints
plug MyAppWeb.Plugs.RateLimit, [max: 5, period: 60], only: [:show]
def show(conn, %{"email" => email}) do
# Always query with a placeholder to avoid timing leaks
normalized_email = String.downcase(String.trim(email))
case Dynamo.get_user_by_email("placeholder@example.com") do
{:ok, _} -
-> # Return a generic response and log internally for monitoring
conn |> put_status(:unauthorized) |> json(%{error: "unauthorized"})
{:error, _reason} -
-> conn |> put_status(:unauthorized) |> json(%{error: "unauthorized"})
end
end
end
On the DynamoDB side, ensure your table has appropriate provisioned or on-demand capacity and enable encryption at rest. Use IAM conditions to restrict excessive query rates from a single principal when possible, and design partition keys to avoid hot partitions that could make timing anomalies more detectable. The following example shows a simple DynamoDB get call using the AWS SDK for Elixir (via ex_aws) with robust error handling that does not distinguish between missing items and other errors:
defmodule MyApp.Dynamo do
@moduledoc false
import ExAws.Dynamo
def get_user_by_email(email) do
table_name = "users"
key = %{"email" => email}
query(table_name, key)
|> ExAws.request()
|> handle_response()
end
defp handle_response({:ok, %{items: []}}) do
{:ok, nil}
end
defp handle_response({:ok, %{items: [item]}}) do
{:ok, item}
end
defp handle_response({:error, _reason}) do
# Return a generic error to avoid leaking information
{:ok, nil}
end
end
Additionally, consider using middleware in Phoenix to enforce global rate limits and to mask errors. Monitor suspicious patterns through your application logs without exposing details to the client. MiddleBrick can be used to validate that these controls are in place by scanning your public endpoints and identifying missing rate limiting or inconsistent error handling across authentication flows.