diff options
author | Francesco Rodriguez <lrodriguezsanc@gmail.com> | 2012-09-24 16:22:28 -0500 |
---|---|---|
committer | Francesco Rodriguez <lrodriguezsanc@gmail.com> | 2012-09-24 16:22:28 -0500 |
commit | 2d4b9c76ccb26aaca1e785f0be37633229380cdc (patch) | |
tree | be3a722c2328c1685e007cf80beafdffebb00e52 /guides/source | |
parent | eb876c4d07130f15be2cac7be968cc393f959c62 (diff) | |
download | rails-2d4b9c76ccb26aaca1e785f0be37633229380cdc.tar.gz rails-2d4b9c76ccb26aaca1e785f0be37633229380cdc.tar.bz2 rails-2d4b9c76ccb26aaca1e785f0be37633229380cdc.zip |
update Security guide to reflect mass assignment protection with StrongParameters [ci skip]
Diffstat (limited to 'guides/source')
-rw-r--r-- | guides/source/security.md | 137 |
1 files changed, 79 insertions, 58 deletions
diff --git a/guides/source/security.md b/guides/source/security.md index 3a6a894695..0186386059 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -375,18 +375,25 @@ The common admin interface works like this: it's located at www.example.com/admi Mass Assignment --------------- -WARNING: _Without any precautions `Model.new(params[:model]`) allows attackers to set any database column's value._ +WARNING: _Without any precautions `Model.new(params[:model]`) allows attackers to set +any database column's value._ -The mass-assignment feature may become a problem, as it allows an attacker to set any model's attributes by manipulating the hash passed to a model's `new()` method: +The mass-assignment feature may become a problem, as it allows an attacker to set +any model's attributes by manipulating the hash passed to a model's `new()` method: ```ruby def signup - params[:user] # => {:name => “ow3ned”, :admin => true} + params[:user] # => {:name=>"ow3ned", :admin=>true} @user = User.new(params[:user]) end ``` -Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the `new` method, or `assign_attributes=` a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: +Mass-assignment saves you much work, because you don't have to set each value +individually. Simply pass a hash to the `new` method, or `assign_attributes=` +a hash value, to set the model's attributes to the values in the hash. The +problem is that it is often used in conjunction with the parameters (params) +hash available in the controller, which may be manipulated by an attacker. +He may do so by changing the URL like this: ``` http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 @@ -395,12 +402,19 @@ http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 This will set the following parameters in the controller: ```ruby -params[:user] # => {:name => “ow3ned”, :admin => true} +params[:user] # => {:name=>"ow3ned", :admin=>true} ``` -So if you create a new user using mass-assignment, it may be too easy to become an administrator. +So if you create a new user using mass-assignment, it may be too easy to become +an administrator. -Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the `attributes=` method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The `accepts_nested_attributes_for` declaration provides us the ability to extend mass assignment to model associations (`has_many`, `has_one`, `has_and_belongs_to_many`). For example: +Note that this vulnerability is not restricted to database columns. Any setter +method, unless explicitly protected, is accessible via the `attributes=` method. +In fact, this vulnerability is extended even further with the introduction of +nested mass assignment (and nested object forms) in Rails 2.3. The +`accepts_nested_attributes_for` declaration provides us the ability to extend +mass assignment to model associations (`has_many`, `has_one`, +`has_and_belongs_to_many`). For example: ```ruby class Person < ActiveRecord::Base @@ -414,77 +428,84 @@ Note that this vulnerability is not restricted to database columns. Any setter m end ``` -As a result, the vulnerability is extended beyond simply exposing column assignment, allowing attackers the ability to create entirely new records in referenced tables (children in this case). +As a result, the vulnerability is extended beyond simply exposing column +assignment, allowing attackers the ability to create entirely new records +in referenced tables (children in this case). ### Countermeasures -To avoid this, Rails provides two class methods in your Active Record class to control access to your attributes. The `attr_protected` method takes a list of attributes that will not be accessible for mass-assignment. For example: +To avoid this, Rails provides an interface for protecting attributes from +end-user assignment called Strong Parameters. This makes Action Controller +parameters forbidden until they have been whitelisted, so you will have to +make a conscious choice about which attributes to allow for mass assignment +and thus prevent accidentally exposing that which shouldn’t be exposed. -```ruby -attr_protected :admin -``` - -`attr_protected` also optionally takes a role option using :as which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the :default role. +NOTE. Before Strong Parameters arrived, mass-assignment protection was a +model's task provided by Active Model. This has been extracted to the +[ProtectedAttributes](https://github.com/rails/protected_attributes) +gem. In order to use `attr_accessible` and `attr_protected` helpers in +your models, you should add `protected_attributes` to your Gemfile. -```ruby -attr_protected :last_login, :as => :admin -``` +Why we moved mass-assignment protection out of the model and into +the controller? The whole point of the controller is to control the +flow between user and application, including authentication, authorization, +and, as part of that, access control. -A much better way, because it follows the whitelist-principle, is the `attr_accessible` method. It is the exact opposite of `attr_protected`, because _it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: +Strong Parameters provides two methods to the `params` hash to control +access to your attributes: `require` and `permit`. The former is used +to mark parameters as required and the latter limits which attributes +should be allowed for mass updating using the slice pattern. For example: ```ruby -attr_accessible :name -attr_accessible :name, :is_admin, :as => :admin -``` - -If you want to set a protected attribute, you will to have to assign it individually: +def signup + params[:user] + # => {:name=>"ow3ned", :admin=>true} + permitted_params = params.require(:user).permit(:name) + # => {:name=>"ow3ned"} -```ruby -params[:user] # => {:name => "ow3ned", :admin => true} -@user = User.new(params[:user]) -@user.admin # => false # not mass-assigned -@user.admin = true -@user.admin # => true + @user = User.new(permitted_params) +end ``` -When assigning attributes in Active Record using `attributes=` the :default role will be used. To assign attributes using different roles you should use `assign_attributes` which accepts an optional :as options parameter. If no :as option is provided then the :default role will be used. You can also bypass mass-assignment security by using the `:without_protection` option. Here is an example: +In the example above, `require` is checking whether a `user` key is present or not +in the parameters, if it's not present, it'll raise an `ActionController::MissingParameter` +exception, which will be caught by `ActionController::Base` and turned into a +400 Bad Request reply. Then `permit` whitelists the attributes that should be +allowed for mass assignment. -```ruby -@user = User.new +A good pattern to encapsulate the permissible parameters is to use a private method +since you'll be able to reuse the same permit list between different actions. -@user.assign_attributes({ :name => 'Josh', :is_admin => true }) -@user.name # => Josh -@user.is_admin # => false +```ruby +def signup + @user = User.new(user_params) + # ... +end -@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) -@user.name # => Josh -@user.is_admin # => true +def update + @user = User.find(params[:id] + @user.update_attributes!(user_params) + # ... +end -@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) -@user.name # => Josh -@user.is_admin # => true +private + def user_params + params.require(:user).permit(:name) + end ``` -In a similar way, `new`, `create`, `create!`, `update_attributes`, and `update_attributes!` methods all respect mass-assignment security and accept either `:as` or `:without_protection` options. For example: +Also, you can specialize this method with per-user checking of permissible +attributes. ```ruby -@user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin) -@user.name # => Sebastian -@user.is_admin # => true +def user_params + filters = [:name] + filters << :admin if current_user.try(:admin?) -@user = User.create({ :name => 'Sebastian', :is_admin => true }, :without_protection => true) -@user.name # => Sebastian -@user.is_admin # => true -``` - -A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of: - -```ruby -config.active_record.whitelist_attributes = true + params.require(:user).permit(*filters) +end ``` -This will create an empty whitelist of attributes available for mass-assignment for all models in your app. As such, your models will need to explicitly whitelist or blacklist accessible parameters by using an `attr_accessible` or `attr_protected` declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to use this application config option; run your tests, and expose each attribute (via `attr_accessible` or `attr_protected`) as dictated by your failing tests. - User Management --------------- @@ -669,7 +690,7 @@ A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTM * Allow <strong> instead of removing <script> against Cross-Site Scripting (XSS). See below for details. * Don't try to correct user input by blacklists: * This will make the attack work: "<sc<script>ript>".gsub("<script>", "") - * But reject malformed input + * But reject malformed input Whitelists are also a good approach against the human factor of forgetting something in the blacklist. @@ -1059,7 +1080,7 @@ config.action_dispatch.default_headers.clear Here is the list of common headers: * X-Frame-Options -_'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website. +_'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website. * X-XSS-Protection _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS attack is detected. Set it to '0;' if you want to switch XSS Auditor off(useful if response contents scripts from request parameters) * X-Content-Type-Options |