Bola Idor in Rails with Cockroachdb
Bola Idor in Rails with Cockroachdb — how this combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA), also called Insecure Direct Object References (IDOR), occurs when an API exposes a direct reference to an object (e.g., a record id) and lacks authorization checks that the requesting identity is allowed to access that object. In a Ruby on Rails app using CockroachDB, the combination of ActiveRecord patterns, CockroachDB’s SQL semantics, and developer assumptions about uniqueness and isolation can inadvertently expose IDs that should be unguessable or guarded by authorization.
CockroachDB is a distributed SQL database that provides strong consistency and serializable isolation by default. While this is beneficial for correctness, it does not enforce application-level authorization. Rails developers sometimes rely on find or find_by to retrieve records by id without confirming the current user’s relationship to that record. For example, a route like /organizations/:org_id/projects/:id might load a project via Project.find(params[:id]). If the controller does not verify that the current user belongs to org_id or own the project, an attacker can iterate through numeric or predictable IDs and access projects they should not see. CockroachDB’s behavior does not cause this; rather, the exposure arises when IDs are predictable and authorization checks are omitted or incorrectly scoped.
In multi-tenant setups, developers may scope queries using tenant identifiers (e.g., current_tenant) but mistakenly trust params for scoping alone. Consider a query like Project.where(organization_id: params[:org_id]).find(params[:id]). If params[:org_id] is supplied by the client and not verified against the authenticated context, an attacker can modify org_id to reference another tenant and still fetch records if IDs are globally unique across the table. CockroachDB will return the row if it exists and the SQL condition matches, regardless of tenant boundaries. This becomes a BOLA when the object reference (the project id or the org id) is not coupled with a proper ownership or access control check.
Another common pattern is using UUIDs for public identifiers while storing internal numeric primary keys. If Rails routes expose UUIDs but the lookup uses an unscoped find on the internal key, or if UUIDs are not validated and mapped correctly, attackers might manipulate the mapping to reference other objects. Additionally, default scopes or class methods in models that apply tenant filters can be overridden inadvertently if the query merges user input without re-applying the scope, leading to privilege escalation across tenants in CockroachDB.
Serialization and nested resources amplify the risk. For instance, an endpoint that returns nested associations (e.g., an organization with embedded projects) may fail to validate that the requesting user has rights to each nested project. Because CockroachDB returns results quickly under serializable isolation, the absence of per-object authorization checks is not masked by latency or errors, making BOLA easier to exploit in automated scans.
To detect such issues, scans like those performed by middleBrick analyze the unauthenticated attack surface, checking whether object references can be accessed without proper authorization. They correlate OpenAPI specs with runtime behavior to identify endpoints where IDs are exposed but authorization is missing or incomplete, highlighting findings mapped to OWASP API Top 10 and compliance frameworks.
Cockroachdb-Specific Remediation in Rails — concrete code fixes
Remediation focuses on ensuring every object access includes authorization tied to the authenticated context, using Rails query patterns that correctly incorporate tenant or ownership constraints, and validating identifiers before lookup. Below are concrete, CockroachDB-aware fixes and examples.
1. Always scope by tenant and ownership, never trust params
Do not rely on client-supplied IDs alone. Combine the authenticated user’s or tenant’s identifier with the object lookup. Use strong parameters and verify associations.
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
before_action :set_tenant_and_project, only: [:show, :update, :destroy]
private
def set_tenant_and_project
# Ensure the project belongs to the tenant the user is authorized for
@project = Project
.where(organization_id: current_user.organization_id)
.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Not found' }, status: :not_found
end
end
This ensures that even if an attacker guesses or iterates IDs, they cannot access projects outside their organization. CockroachDB will efficiently use the index on organization_id and the primary key.
2. Use UUIDs safely with explicit mapping and validation
If you use UUIDs as public identifiers, map them to internal keys within a scoped query to avoid enumeration and mapping attacks.
# app/models/project.rb
class Project < ApplicationRecord
before_validation :generate_public_id, on: :create
validates :public_id, presence: true, uniqueness: true
private
def generate_public_id
self.public_id ||= SecureRandom.uuid
end
end
# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def show
@project = Project
.where(organization_id: current_user.organization_id)
.find_by!(public_id: params[:public_id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Not found' }, status: :not_found
end
end
This approach ensures the UUID is validated within the tenant scope. CockroachDB’s index on public_id and organization_id supports efficient lookups without exposing global numeric IDs.
3. Avoid overriding default scopes in a way that bypasses authorization
If you use default scopes for tenancy, ensure joins and includes reapply the scope and do not allow unscoped queries in controller actions.
# app/models/project.rb
class Project < ApplicationRecord
belongs_to :organization
default_scope { where(organization_id: ->(project) { Project.arel_table[:organization_id] }) }
# Prefer explicit scopes for clarity
scope :for_organization, ->(org_id) { where(organization_id: org_id) }
end
# app/controllers/projects_controller.rb
class ProjectsController <ApplicationController
def index
@projects = current_user.organization.projects_for_organization(current_user.organization_id)
end
end
Using explicit scopes in the controller and model keeps authorization checks visible and reduces the risk of accidentally bypassing them.
4. Validate nested associations and include authorization in serializers
When returning nested resources, ensure each nested object is authorized. Avoid exposing nested records unless the parent relationship is verified.
# app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
def show
org = Organization.includes(:projects).find_by!(slug: params[:slug])
authorize_organization(org) # Pundit or similar policy check
render OrganizationSerializer.new(org).serialized_json
end
private
def authorize_organization(org)
unless org.users.exists?(current_user.id)
render json: { error: 'Forbidden' }, status: :forbidden
end
end
end
These patterns ensure that even with CockroachDB’s fast distributed queries, each object access is validated against the requesting identity.
middleBrick can scan the unauthenticated attack surface of your Rails endpoints, including how IDs and tenant parameters are handled, and surface BOLA findings with remediation guidance tied to frameworks such as OWASP API Top 10. Its OpenAPI/Swagger analysis, with full $ref resolution, cross-references spec definitions with runtime behavior to highlight missing authorization on object references.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |