Bola Idor in Rails
How BOLA/IdOR Manifests in Rails
BOLA/IdOR vulnerabilities in Rails applications often stem from ActiveRecord's convenience methods combined with Rails's RESTful routing conventions. The most common pattern involves controller actions that use params[:id] directly to find database records without verifying the current user's authorization.
Consider a typical Rails controller:
class DocumentsController < ApplicationController
def show
@document = Document.find(params[:id])
end
endThis code appears harmless but is vulnerable. An attacker can simply change the ID in the URL to access any document in the database. If user A has document with ID 123 and user B has document with ID 456, user B can access user A's document by visiting /documents/123.
Rails's mass assignment features create another attack vector. When using update_attributes or update with strong parameters, developers often forget to scope the query:
def update
@document = Document.find(params[:id])
@document.update(document_params)
endThe vulnerability becomes more severe when combined with Rails's default parameter handling. If your application uses JSON APIs, an attacker can send additional parameters that get processed by ActiveRecord:
# Vulnerable to IDOR via nested attributes
def update
@user = User.find(params[:id])
@user.update(user_params)
endActiveRecord's dynamic finders and the deprecated find_by_id method also contribute to BOLA/IdOR issues when used without proper authorization checks.
Another Rails-specific pattern involves has_many associations. Developers often write code like:
def index
@documents = current_user.documents
endWhich is secure, but then create an edit action that isn't properly scoped:
def edit
@document = Document.find(params[:id]) # Vulnerable!
endThe contrast between the secure index action and the vulnerable edit action makes these vulnerabilities particularly insidious in Rails applications.
Rails-Specific Detection
Detecting BOLA/IdOR vulnerabilities in Rails requires examining both the code structure and runtime behavior. Static analysis can identify risky patterns, but dynamic testing is essential for comprehensive coverage.
Code-level indicators include:
- Controller actions using
Model.find(params[:id])without authorization checks - Missing
before_action :authenticate_user!or similar authentication filters - Update actions that don't verify record ownership before modification
- JSON API controllers that accept ID parameters without scoping
middleBrick's Rails-specific scanning analyzes these patterns automatically. When you submit a Rails API endpoint, middleBrick:
- Identifies controller actions and their parameter handling
- Checks for authentication middleware presence
- Analyzes database query patterns for ID-based lookups
- Tests authenticated vs unauthenticated access to the same resource
The scanner's active testing phase attempts to access resources using different user contexts. For example, if your API has a user profile endpoint, middleBrick will:
# Test pattern: access /users/1 when authenticated as user 2
curl -H "Authorization: Bearer user2_token" https://api.example.com/users/1middleBrick also analyzes your OpenAPI/Swagger specification if provided, cross-referencing documented authentication requirements with actual runtime behavior. This catches cases where documentation claims authentication is required but the implementation has gaps.
For Rails applications using Devise or other authentication gems, middleBrick specifically tests the integration between the authentication system and resource access patterns. It verifies that current_user is properly used to scope database queries.
The scanner's 12 parallel security checks include a dedicated BOLA/IdOR assessment that:
- Tests resource enumeration by incrementing ID parameters
- Attempts access with different user credentials
- Checks for information disclosure through error messages
- Verifies proper authorization headers are enforced
Results are presented with Rails-specific remediation guidance, showing exactly which controller actions need authorization checks and what code changes are required.
Rails-Specific Remediation
Securing Rails applications against BOLA/IdOR requires a combination of architectural patterns and Rails-specific features. The most effective approach is to never use raw ID parameters for record lookup.
Pundit is the Rails community's preferred authorization framework. Here's how to implement it for BOLA/IdOR prevention:
# app/policies/document_policy.rb
class DocumentPolicy < ApplicationPolicy
def show?
record.user == user
end
endThen in your controller:
class DocumentsController < ApplicationController
before_action :set_document, only: [:show, :edit, :update, :destroy]
before_action :authorize_document, only: [:show, :edit, :update, :destroy]
def show
# Pundit handles authorization
end
private
def set_document
@document = Document.find(params[:id])
end
def authorize_document
authorize @document
end
endRails's Concerns feature helps organize authorization logic:
# app/controllers/concerns/authenticated_resource.rb
module AuthenticatedResource
extend ActiveSupport::Concern
included do
before_action :set_and_authorize_resource
end
def set_and_authorize_resource
resource = controller_name.classify.constantize
instance_variable_set("@#{controller_name.singularize}",
resource.find(params[:id]))
authorize instance_variable_get("@#{controller_name.singularize}")
end
endFor JSON APIs, use Pundit's API-friendly helpers:
def show
@document = current_user.documents.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Not found or unauthorized' }, status: :not_found
endThe key pattern is scoping all queries to the current user. Instead of:
@document = Document.find(params[:id])Use:
@document = current_user.documents.find(params[:id])This leverages ActiveRecord's query interface to ensure users can only access their own records. The find method will raise RecordNotFound if the record doesn't exist or doesn't belong to the current user.
For has_many :through associations, be particularly careful:
# Vulnerable
@project = Project.find(params[:id])
@tasks = @project.tasksShould be:
@project = current_user.projects.find(params[:id])
@tasks = @project.tasksRails's default route helpers can also help prevent BOLA/IdOR by using resource-based URLs instead of ID-based ones, though this is more of a convention than a security feature.
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 |