Broken Access Control in Phoenix with Dynamodb
Broken Access Control in Phoenix with Dynamodb — how this specific combination creates or exposes the vulnerability
Broken Access Control (BOLA/IDOR) in a Phoenix application that uses DynamoDB often arises when authorization checks are performed only in the application layer or when API parameters are directly mapped to DynamoDB queries without strict ownership verification. In such setups, an authenticated user might be able to manipulate request parameters—such as a user ID or record ID—to access or modify data that belongs to another user.
Consider a typical Phoenix controller that retrieves a user profile by ID and constructs a DynamoDB query using the provided parameter without verifying that the requested ID matches the authenticated user’s ID:
defmodule MyAppWeb.ProfileController do
use MyAppWeb, :controller
def show(conn, %{"user_id" => user_id}) do
# Unsafe: directly using user-supplied ID to query DynamoDB
case MyApp.Dynamo.get_user_profile(user_id) do
{:ok, profile} -> json(conn, profile)
{:error, _} -> send_resp(conn, 404, "Not found")
end
end
end
If the endpoint relies on an unverified user_id from the request, an attacker can change this value to access other users’ profiles. DynamoDB itself does not enforce application-level ownership; it enforces permissions at the AWS identity and policy level. If the IAM role used by the Phoenix service has broad read permissions on the table, any valid record ID can be retrieved as long as the request reaches DynamoDB.
This becomes a BOLA issue when authorization is missing or incomplete. For example, even if the caller is authenticated via a session or token, there is no check ensuring that the resource being requested is associated with the authenticated subject. In a multi-tenant setup, an attacker might also leverage predictable IDs (sequential integers or UUIDs) to enumerate other accounts.
In the context of middleBrick’s LLM/AI Security checks, systems that expose endpoints with such weaknesses may also leak system prompts or allow prompt injection if LLM endpoints are involved. The scanner tests whether an unauthenticated LLM endpoint is reachable and whether outputs expose sensitive patterns, which can complement findings about authorization gaps.
Additionally, DynamoDB’s design encourages storing partition keys that align with access patterns. If the partition key is chosen without considering access boundaries (e.g., using a tenant ID or user ID), it can be trivial for an attacker to iterate over known keys. MiddleBrick’s BOLA/IDOR checks are designed to detect such patterns by correlating spec definitions with runtime behavior, ensuring that endpoints enforce proper ownership checks.
To summarize, the combination of Phoenix controllers that trust client-supplied identifiers and DynamoDB’s permission model—where authorization is managed outside the database—creates a surface where BOLA/IDOR can occur if application logic does not enforce strict ownership and context validation on every request.
Dynamodb-Specific Remediation in Phoenix — concrete code fixes
Remediation centers on ensuring that every DynamoDB request is constrained to the data the authenticated subject is allowed to access. This means deriving resource ownership from the authenticated session and enforcing it before constructing queries or commands.
First, resolve the authenticated subject in the pipeline and pass it explicitly to data access functions. For example, using Guardian for JWT-based authentication:
defmodule MyAppWeb.ProfileController do
use MyAppWeb, :controller
plug MyAppWeb.Plugs.Authenticate when action in [:show, :update]
def show(conn, %{"user_id" => user_id}) do
# The authenticated subject is available from the connection after plug
subject_id = conn.assigns.current_user.id
# Enforce ownership: do not trust the request user_id
if subject_id != user_id do
send_resp(conn, 403, "Forbidden")
else
case MyApp.Dynamo.get_user_profile(subject_id) do
{:ok, profile} -> json(conn, profile)
{:error, _} -> send_resp(conn, 404, "Not found")
end
end
end
end
Second, design DynamoDB access functions to accept the subject identifier and construct keys accordingly, avoiding any direct passthrough of user input:
defmodule MyApp.Dynamo do
@table "UserProfiles"
def get_user_profile(subject_id) when is_binary(subject_id) do
key = %{
"PK" => "USER##{subject_id}",
"SK" => "PROFILE"
}
MyApp.Dynamo.get_item(@table, key)
end
def list_user_posts(subject_id, opts \ []) do
query_key = %{
"PK" => "USER##{subject_id}",
"SK" => begin
opts[:start_key] || "POST#"
end
}
MyApp.Dynamo.query(@table, query_key, opts)
end
end
Third, consider using a policy layer or context module that encapsulates authorization rules. This keeps controllers thin and ensures consistent checks across endpoints:
defmodule MyApp.Profiles do
alias MyApp.Dynamo
def get_profile_for(subject_id, requested_id) when subject_id == requested_id do
Dynamo.get_user_profile(requested_id)
end
def get_profile_for(_subject_id, _requested_id) do
{:error, :forbidden}
end
end
Finally, validate and sanitize all inputs that influence DynamoDB key construction. Even with ownership checks, enforce allowed patterns for IDs (e.g., UUID format) to prevent injection or enumeration attacks. MiddleBrick’s Property Authorization and Input Validation checks can help identify missing constraints and unsafe consumption patterns in your API surface.
These steps ensure that DynamoDB queries are scoped to the authenticated subject, mitigating BOLA/IDOR risks in Phoenix applications. Remember that middleBrick detects and reports such issues without fixing them; it provides findings with remediation guidance to help you implement these controls.