Broken Authorization in Ruby
Vulnerable example
The following Ruby snippet shows a Ruby on Rails controller that pulls the user from the request parameter, looking up the user email passed as a parameter in the URL:
def restricted
@user = User.find_by(email: params[:email])
if !(@user)
flash[:error] = "Sorry, invalid user"
redirect_to public_index_path
end
end
This could be abused by any user to access the restricted API by invoking it, passing the email address of another legitimate user as a parameter.
Prevention
Ruby on Rails
User management and authentication are not native features of Rails, but they can easily be added by either writing the code or adding a gem, such as Devise.
Authorized user
A simple manual approach is to add a method that retrieves the current, logged-in user looking up active sessions to the Application Controller. It is frequent to add e.g. current_user
method in the ApplicationController
class to make sure every controller in the application inherits it.
class ApplicationController < ActionController::Base
def current_user
User.find_by(id: session[:user_id])
end
end
Such method takes the user ID value from the user’s session and is resilient against tampering. It can be easily invoked by other controllers such as the one shown in this snippet:
def restricted
@user = current_user
if !(@user)
flash[:error] = "Sorry, invalid user"
redirect_to public_index_path
end
end
Role-based authorization
It’s common to set access restrictions based on roles in order to check if a user has a specific role (such as administrator) and either allow access or redirect with an “Access Denied” message. Roles are attributes associated with a user account and implemented in a User schema and model.
The simplest scenario is a binary role scenario when a user can be either an administrator or an ordinary user. Add a boolean attribute to the User
schema to indicate whether a user is an administrator or not and check the role checking the @user.admin
attribute value:
def restricted
@user = current_user
if !(@user) || !(@user.admin)
flash[:error] = "Sorry, the user is not an administrator"
redirect_to public_index_path
end
end
Controller filters
Role-base controls can be also enforced using “before” filters in the Controller
classes in order to halt the request cycle if a condition is not met. In the following snippet, the restricted
method can be invoked only by users who satisfy the administrative
filter:
class AdminController < ApplicationController
before_filter :administrative
def restricted
@user = current_user
if !(@user) || !(@user.admin)
flash[:error] = "Sorry, the user is not an administrator"
redirect_to public_index_path
end
end
private
def administrative
if !current_user.admin
redirect_to public_index_path
end
end
end
Filters are inherited, so if you set a filter on ApplicationController
, it will be run on every controller in your application.
References
Rails Guides - Action Controller Overview Rails Guides - Securing Rails Applications RailsApps Project - Rails Authorization Devise