2013/08/19

Rails4のMass Assignment脆弱性対策のStrong Parametersについて

Mac OSX10.8でrbenvを使ってRuby1.9とRails4.0をインストールする
ひょんなことから Rails4 をインストールしてしまった俺達は
attr_accessibleattr_protectedを使わずに
Strong Parameters を使うことになってしまっていた!
ActionController::StrongParameters
rails/strong_parameters

初めての Rails で右も左も分からない状況で
いきなり Strong Parameters と言われてもよくわからないので以下についてまとめます。

Mass Assignment とは

Rails ではこんなコードをよく見ると思います。
def create
  @user = User.new(params[:user])
  unless @user.save
    render 'new'    
  end
end
Userモデルにnameemailという属性が設定されていて、
params[:user]{name: 'willnet', email: 'willnet@example.com'}のような
パラメータが入っていたとき、上記のUser.new(params[:user])で一度にnameemail
設定されます。これが mass assignment です。属性が多いときなど大変便利です。
Rails4.0に含まれる strong_parameters について - willnet.in

Mass Assignment 脆弱性

先ほどの例ではnameemailという属性が設定されていました。
この2つの属性の他に、システム側で自動で割り振られるtokenという属性が存在したとします。
tokenはユーザには変更させたくありません。

さて、 それではUser#nameUser#emailを変更するための
update アクションを書いてみましょう。
edit の view には name と email を変更するための text_field だけが存在するとします。
def update
  @user = User.find(params[:id])
  unless @user.update_attributes(params[:user])    render 'edit'&
  end
end
上記のコードは基本的にはうまく動くはずです。ただしユーザが普通にブラウザを使う限りは。
  • chromeのデベロッパーツールなどで
    <input type="text" name="user[token]" value="hoge" />などのタグを
    form中に生成してsubmitする
  • curlやtelnetなどで直接user[token]=hogeのようなリクエストを、
    通常のリクエストに追加して送信する
上記のような方法を使った場合、ユーザに編集させたくないtoken属性が更新されてしまいます。
これが mass assignment の脆弱性と呼ばれるものです。
Rails4.0に含まれる strong_parameters について - willnet.in

Rails4 以前の Mass Assignment 脆弱性対策

attr_accessibleattr_protectedが提供されていました。
Rails2系(および3.0)だと後述のroleが設定できないため、
User#nameだけ更新したい場合とUser#nameとUser#email両方を更新させたい場合の
2種類があるケースなど、同じモデルに複数種類の mass assignment 脆弱性対策をかけたくなります。
Rails4.0に含まれる strong_parameters について - willnet.in
Rails 3.1以上からはattr_accessibleattr_protectedに role という機能が導入されました。
class User < ActiveRecord::Base
  attr_accessible :name
  attr_accessible :name, :email, as: :name_and_email
end
as オプションで role の名前を指定して、
@user.update_attributes(params[:user], role: 'name_and_email')
のように role オプションで適用したいattr_accessible(もしくはattr_protected)を指定します。
role がない場合はデフォルトのもの(上記のケースはattr_accessible :name)が使用されます。

これにより大分きちんと書けるようになったのですが、
多くの role を定義する必要があるとやや煩雑になります。
Rails4.0に含まれる strong_parameters について - willnet.in
role の設定が煩雑になった結果(?)、
GitHub、Mass Assignment利用の脆弱性を突かれるという事例が発生したので
この煩雑な role 設定の管理をしなくてはいけない状況をどうにかするべく
Strong Parameters が導入されたようです。

Rails4 の Mass Assignment 脆弱性対策の Strong Parameters について

rails generate scaffold user name:string email:string
scaffold を使用すると、Strong Parameters の機能を使用したコードが生成されます。
class UsersController < ApplicationController
  ...
  # POST /users
  # POST /users.json
  def create
    @user = User.new(user_params)
    ...
  end
  ...
  private
  ...
    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      params.require(:user).permit(:name, :email)
    end
end
このコードは「params が :user というキーを持ち、params[:user] は :name 及び :email というキーを持つハッシュであること」を検証します。

例えば、params[:user]{:name => 'taro', :email => 'taro@example.com'}のようなハッシュである場合に検証 OK となります。
» Rails4 の Strong Parameters でリクエストパラメータを検証する TECHSCORE BLOG

許可していないパラメータが渡された場合はデフォルトでは無視される

試しに user_params の内容を以下のように修正してみます。
# Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      # 試に :email の指定を消す.
      # params.require(:user).permit(:name, :email)
      params.require(:user).permit(:name)
    end
この状態で再度ユーザ登録を行うと、リクエストパラメータとして名前(name)とメールアドレス(email)が渡されますが、メールアドレス(email)は無視されます。

ログを確認すると以下のようなメッセージが出力されていました。
Unpermitted parameters: email
» Rails4 の Strong Parameters でリクエストパラメータを検証する TECHSCORE BLOG

許可しないパラメータが渡された場合に例外を発生させる

許可しないパラメータが渡された場合の動作は config/application.rb で変更することができます。

試に例外を発生させるように変更してみます。
# デフォルトは :log で, 許可されていないパラメータは無視されたうえでログ出力されます.
config.action_controller.action_on_unpermitted_parameters = :raise
この状態で再びユーザ登録を行うと(リクエストパラメータに name と email が渡されると)、ActionController::UnpermittedParameters という例外が raise されました。
» Rails4 の Strong Parameters でリクエストパラメータを検証する TECHSCORE BLOG

ネストしたパラメータ

Strong Parameters のドキュメントを読むと、permit メソッドの引数を以下のように指定することで、
ネストしたパラメータに対応することができるようです。
params.permit(:name,
              {:emails => []},
              :friends => [:name,
                           {:family => [:name]}])
» Rails4 の Strong Parameters でリクエストパラメータを検証する TECHSCORE BLOG

全てのパラメータを許可する

開発中のときなど、全ての属性を許可したいケースがあると思いますが、
その時はparams[:user].permit!のようにします。
ただ、permit!が今のバージョン(0.1.4)だと再帰的に効かないという話しがあるので
(RailsCastsで言ってた。未確認)、うまくいかない場合は github 上の最新を使ってみてください。
Rails4.0に含まれる strong_parameters について - willnet.in

参考

Rails4.0に含まれる strong_parameters について - willnet.in
» Rails4 の Strong Parameters でリクエストパラメータを検証する TECHSCORE BLOG

参考というより引用元です。
どちらの記事も読み返したくなる記事内容で Chrome のタブを閉じることができなかったので
内容の9割以上が引用でしたが、記事にすることでタブを閉じることができそうです。
Rails やるときにまた見に行くと思います。
どちらもわかりやすい記事をありがとうございます。

タグ(RSS)

ブログ アーカイブ