HIGH broken access controlphoenixfirestore

Broken Access Control in Phoenix with Firestore

Broken Access Control in Phoenix with Firestore — how this specific combination creates or exposes the vulnerability

Broken Access Control (BOLA/IDOR) occurs when an API fails to enforce proper authorization checks between a user and the resources they are attempting to access. In a Phoenix application using Google Cloud Firestore, this typically arises when route handlers or controller actions reference a resource identifier (e.g., a document ID) supplied by the client without verifying that the authenticated subject has permission to operate on that specific document or collection.

Firestore security rules are intended to enforce access control, but they are often misconfigured or bypassed when the application layer does not perform its own authorization checks. In Phoenix, a developer might write a controller that directly uses a user-supplied id to fetch or update a document without ensuring the subject owns or is permitted to access that document. For example, an endpoint like /users/:id/profile might use Plug.Params to bind the :id and then call Firestore to read or write without confirming the requesting user matches the :id. Because Firestore rules may be permissive for authenticated users (e.g., allowing reads/writes if request.auth != null), a missing authorization check in the application can lead to horizontal privilege escalation: one user can view or modify another user’s data simply by changing the ID in the request.

Phoenix APIs commonly expose Firestore document IDs in URLs or JSON responses. If an endpoint does not validate ownership or role-based permissions, this becomes a classic BOLA/IDOR vector. Consider an administrative endpoint that deletes a Firestore document using an ID from the request. Without proper checks, an attacker can iterate through valid document IDs to delete or read other users’ records. Even with Firestore rules in place, permissive rules like allow read, write: if request.auth != null; do not enforce tenant isolation; they only confirm the user is authenticated. The application must add explicit checks, such as ensuring a user_id field in the document matches the authenticated subject’s UID.

The combination of Phoenix’s flexible routing and Firestore’s document model amplifies the risk. Routes can map directly to document paths, and if parameter binding is too permissive, it becomes trivial to tamper with identifiers. Additionally, Firestore queries that lack strict filters (e.g., querying a collection without scoping to a user ID) can return documents the caller should not see. Insecure direct object references are further exposed when Firestore document IDs are predictable or leaked in API responses, client-side code, or logs. Attack patterns such as ID enumeration, forced browsing, and tampering with JWT or session identifiers can then be chained to exploit weak authorization boundaries in the Phoenix layer.

To detect such issues, middleBrick runs a BOLA/IDOR check as one of its 12 parallel security assessments. It probes endpoints with different identifiers and authentication contexts to determine whether authorization is enforced consistently. Findings include evidence of missing ownership validation, overly permissive Firestore rules, and endpoints that expose document identifiers without scoping. Remediation requires implementing server-side authorization in Phoenix, scoping Firestore queries to the authenticated subject, and validating that every document access is preceded by an explicit check rather than relying solely on rules.

Firestore-Specific Remediation in Phoenix — concrete code fixes

Remediation focuses on ensuring every data access in Phoenix is explicitly scoped and authorized. Firestore security rules are a necessary layer but are not sufficient on their own; the application must enforce tenant isolation and role-based access in its business logic.

1) Scope Firestore queries to the authenticated user. Instead of retrieving a document by an untrusted ID, derive the document path from the authenticated subject. For example, if your user documents are stored under users/{uid}, use the subject’s UID from the session or token to construct the reference:

def get_user_profile(conn, _params) do
  user_id = conn.assigns.current_user.id
  doc_ref = Firestore.document("users/#{user_id}")
  case Firestore.get(doc_ref) do
    {:ok, snapshot} ->
      render(conn, "profile.json", data: snapshot.data)
    {:error, :not_found} ->
      send_resp(conn, :not_found, "Not found")
  end
end

This ensures the user can only access their own profile, regardless of what ID they might supply in the request parameters.

2) Validate ownership before writes. When an endpoint accepts a document ID, cross-check it against the authenticated subject. For instance, when updating a user’s settings, fetch the document first and confirm the document’s user_id field matches the subject:

def update_settings(conn, %{"id" => doc_id} = params) do
  user_id = conn.assigns.current_user.id
  doc_ref = Firestore.document("settings/#{doc_id}")
  with {:ok, snapshot} <- Firestore.get(doc_ref),
       true <- snapshot.exists?, 
       %{data: %{"user_id" => doc_user_id}} when doc_user_id == user_id <- snapshot.data do
    Firestore.update(doc_ref, params)
    send_resp(conn, :no_content, "")
  else
    _ -> send_resp(conn, :forbidden, "Access denied")
  end
end

This two-step pattern (read to validate, then write) prevents attackers from substituting another document ID.

3) Use Firestore queries with explicit filters instead of direct document references when listing collections. For example, to list a user’s posts, scope the query to the user ID rather than fetching all documents and filtering client-side:

def list_user_posts(conn, _params) do
  user_id = conn.assigns.current_user.id
  query = Firestore.collection("posts") 
             |> Firestore.where("user_id", "==", user_id)
  {:ok, result} = Firestore.get_all(query)
  render(conn, "index.json", data: result)
end

Ensure Firestore security rules also align by requiring the user ID field to match request.auth.uid, but never rely on rules alone. Combine rules with application-level checks for defense in depth.

4) Avoid exposing Firestore document IDs in APIs where they can be tampered with. If you must expose identifiers, use opaque tokens or map them server-side to ensure they cannot be iterated. For example, map a public-facing key to a Firestore document ID only after validating permissions:

def get_public_resource(conn, %{"public_key" => key}) do
  user_id = conn.assigns.current_user.id
  # Map public_key to a Firestore document ID safely after checking access
  with {:ok, doc_id} <- ResourceMapper.get_document_id(key, user_id) do
    doc_ref = Firestore.document("resources/#{doc_id}")
    Firestore.get(doc_ref)
  end
end

By tying identifiers to the authenticated subject and validating on every request, you mitigate BOLA/IDOR risks. middleBrick’s checks include verifying that endpoints do not trust client-supplied identifiers without server-side authorization, and that Firestore queries are scoped to the subject. These practices reduce the attack surface and align with OWASP API Top 10 A01:2023 — Broken Access Control.

Frequently Asked Questions

Why can't Firestore security rules alone prevent Broken Access Control in Phoenix?
Firestore rules validate requests at the database layer but do not enforce application-level authorization such as ensuring a user can only access their own documents. If Phoenix endpoints use untrusted IDs without checking ownership, rules alone cannot prevent horizontal IDOR because they lack context about the requester's intended resource.
How does middleBrick detect Broken Access Control in a Phoenix and Firestore setup?
middleBrick tests endpoints with different identifiers and authentication states to see whether authorization is enforced. It checks whether document access is scoped to the authenticated subject, whether Firestore queries are properly filtered, and whether application logic validates ownership before read/write operations.