Published by Dan Cunning on Mar 14, 2012
Archived This article was written prior to DHH releasing the strong_parameters gem, which also moves the responsibility from the model to controller, though it doesn't use a *_filter
approach. I suggest you use strong_parameters
.
On March 2012, homakov pushed a commit that launched a thousand opinions. He gamed the github.com security system to give himself write access to the Rails repository. The security flaw he exposed is a well-documented Rails security consideration, but what caused such an uproar was the victim: github.com, one of the flagship Ruby on Rails development teams. If they can accidently expose such a security hole then anyone can. Could Rails provide more help?
Proposed solutions normally center around how Rails could strengthen attr_accessible
. Here's one of the more thorough descriptions of the problem and how to prevent it. At the end of the article, he links to a gist by Yehuda Katz that brings up the central point: user supplied parameters are a concern of the controller not the model.
Quick Sidebar: I use attr_accessible
when the assignment is handled within the model, such as an id, created_at, updated_at, and counter_caches. Controllers, seed files and unit tests never need to set these attributes, but they could by explicitly using the attribute writer.
The param_protected gem adds param_protected
and param_accessible
to ActionController::Base
which override params
. It supports parameter nesting, regexes and if, unless, only, and except options. It takes a large step in the right direction: now the protection is in the proper class!
But I think param_protected falls short:
I decided to fix my problems with param_protected, but since I don't want the param_protected method and I wanted to change the internal implementation, I made an entirely new gem: param_accessible.
The gem integrates into your Rails application with a before_filter and supports nested attributes, regexes and the if, unless, only, and except options. It also stops any request with invalid parameters by throwing an exception, which can be handled the same way as ActiveRecord::RecordNotFound, giving the developer flexibility and the client a detailed explanation of what they did wrong and how to fix it.
For more information see the example code below or check out the gem here. It should get your application secure quickly, easily, and in a friendly manner.
#
# app/controllers/application_controller.rb
#
class ApplicationController < ActionController::Base
# make create and update actions across your application secure by default
before_filter :ensure_params_are_accessible, only: %i[create update]
# expose your common application parameters
param_accessible :page, :sort
# this error is thrown when the user submits an inaccessible param
rescue_from ParamAccessible::Error, with: :handle_param_not_accessible
protected
def handle_param_not_accessible(e)
flash[:error] = "You gave me some invalid parameters: #{e.inaccessible_params.join(", ")}"
redirect_to :back
end
end
#
# app/controllers/users_controller.rb
#
class UsersController < ApplicationController
# these attributes are available for everyone
param_accessible user: %i[name email password password_confirmation]
# attributes are only available if the controller instance method is_admin? is true
param_accessible user: %i[is_admin is_locked_out], if: :is_admin?
def update
@user = User.find(params[:id])
# this is now safe!
if @user.update(params[:user])
# your success code
else
# your failure code
end
end
end
#
# app/controllers/demo_controller.rb
#
class DemoController < ApplicationController
# rescue_from ParamAccessible::Error and respond with a 406 Not Acceptable status
# and HTML, JSON, XML, or JS compatible explanation of which parameters were invalid
include ParamAccessible::NotAcceptableHelper
param_accessible :foo, if: :is_admin
param_accessible :bar, unless: :logged_in?
param_accessible :baz, only: :show
param_accessible :nut, except: :index
end
#
# app/controllers/insecure_controller.rb
#
class InsecureController < ApplicationController
# skip the filter in ApplicationController to avoid the accessible parameter checks
skip_before_filter :ensure_params_are_accessible
end
I'm a Ruby on Rails contractor from Atlanta GA, focusing on simplicity and usability through solid design. Read more »