Broken Access Control in Grape
How Broken Access Control Manifests in Grape
Broken Access Control in Grape APIs typically emerges through three primary attack vectors that exploit the framework's flexible routing and parameter handling. The most common pattern involves improper parameter validation where attackers manipulate IDs or UUIDs to access resources they shouldn't have permissions for. Consider this vulnerable Grape endpoint:
module API
class Products < Grape::API
get '/products/:id' do
Product.find(params[:id])
end
end
end
This endpoint exposes a classic IDOR (Insecure Direct Object Reference) vulnerability. An attacker can simply increment the ID parameter to access any product in the database, regardless of ownership or permissions. The issue compounds when combined with Grape's flexible parameter coercion, which automatically converts string parameters to integers without validation.
Another frequent manifestation occurs in nested resource endpoints where authorization checks are inconsistently applied. Many developers implement authorization in the outer resource but forget to apply the same checks to nested endpoints:
module API
class Users < Grape::API
before { authenticate! }
get '/users/:user_id/products' do
user = User.find(params[:user_id])
Product.where(user_id: user.id)
end
get '/users/:user_id/products/:id' do
Product.find(params[:id]) # Missing authorization check!
end
end
end
The second endpoint allows any authenticated user to access any product by ID, completely bypassing the user ownership check. This pattern is particularly dangerous because it's easy to miss during code reviews and often passes initial testing if testers only use their own IDs.
Property-level authorization failures represent a more subtle but equally dangerous variant. Developers often forget to filter sensitive attributes when returning objects:
module API
class Admin < Grape::API
get '/users/:id' do
User.find(params[:id])
end
end
end
If this endpoint lacks proper authorization checks, any authenticated user could access admin-only user attributes like email, phone number, or even hashed passwords. Grape's entity system can help mitigate this, but only if properly configured with role-based attribute filtering.
Grape-Specific Detection
Detecting Broken Access Control in Grape APIs requires a multi-layered approach combining static analysis, runtime testing, and automated scanning. Static analysis tools can identify patterns like missing authorization checks, inconsistent parameter validation, and improper use of Grape's before filters. Look for these red flags in your codebase:
Missing Authorization Checks
# Dangerous pattern - no authorization
get '/resource/:id' do
Resource.find(params[:id])
end
Inconsistent Parameter Handling
# Mixed parameter types without validation
params do
requires :id, type: Integer
end
get '/resource/:id' do
# Integer coercion happens, but is it safe?
Resource.find(params[:id])
end
For runtime testing, middleBrick provides specialized Grape API scanning that tests unauthenticated endpoints for broken access control vulnerabilities. The scanner automatically generates parameter variations to test for IDOR vulnerabilities, attempts to access nested resources without proper authorization, and verifies that sensitive properties are properly filtered. Unlike generic API scanners, middleBrick understands Grape's routing conventions and parameter handling, making it particularly effective at finding framework-specific vulnerabilities.
middleBrick's detection capabilities include:
- IDOR Testing: Systematically tests parameter manipulation across all endpoints to identify broken access control
- Property Authorization: Verifies that sensitive attributes are properly filtered based on user roles
- Authentication Bypass: Tests whether endpoints can be accessed without proper authentication
- Privilege Escalation: Attempts to access admin-only endpoints or perform privileged actions
The scanner runs in under 15 seconds and provides a security score with detailed findings, including the specific parameters and endpoints that are vulnerable. This rapid feedback loop makes it ideal for integrating into your development workflow before code reaches production.
Grape-Specific Remediation
Remediating Broken Access Control in Grape requires a defense-in-depth approach that combines proper authorization patterns, parameter validation, and attribute filtering. The foundation is implementing consistent authorization checks using Grape's before filters or middleware:
Centralized Authorization
module API
class Base < Grape::API
before do
authenticate! # Ensure user is logged in
authorize! # Check permissions for this resource
end
# Authorization helper
def authorize!(resource = nil)
raise Unauthorized unless current_user.can_access?(resource)
end
end
end
This pattern ensures every endpoint goes through authentication and authorization checks. For resource-specific authorization, you can implement more granular checks:
Resource-Level Authorization
module API
class Products < Grape::API
before { authenticate! }
params do
requires :id, type: Integer
end
get '/products/:id' do
product = Product.find(params[:id])
authorize!(product) # Check if user can access this specific product
present product, with: ProductEntity
end
end
end
Parameter validation is equally critical. Grape's strong parameters feature should be used to validate and sanitize all inputs:
Strong Parameters
module API
class Products < Grape::API
params do
requires :id, type: Integer, desc: 'Product ID'
# Additional validation rules
exactly(:id, message: 'Invalid product ID format')
end
get '/products/:id' do
# params[:id] is now guaranteed to be a valid integer
product = Product.find(params[:id])
authorize!(product)
present product, with: ProductEntity
end
end
end
Property-level authorization using Grape entities provides the final layer of defense:
Entity-Based Attribute Filtering
class ProductEntity < Grape::Entity
expose :id
expose :name
expose :price
# Only expose these to admins
expose :internal_notes, if: { admin: true }
expose :cost, if: { admin: true }
def admin?
# Grape automatically passes current_user as a root context variable
options[:admin] || current_user.admin?
end
end
For comprehensive protection, implement a policy layer that separates authorization logic from your API endpoints:
Policy Pattern
class ProductPolicy
def initialize(user, product)
@user = user
@product = product
end
def show?
@user.admin? || @product.user_id == @user.id
end
def update?
@user.admin? || @product.user_id == @user.id
end
end
Then use this in your Grape endpoints:
def show
product = Product.find(params[:id])
raise Forbidden unless ProductPolicy.new(current_user, product).show?
present product, with: ProductEntity
end
This layered approach ensures that even if one control fails, others provide defense-in-depth protection against broken access control vulnerabilities.