Rails-2.0がリリースされて半年が過ぎ、既にRails-2.1もリリースされているというのにRubricksはいまだにRails-1.2.6で動いている。というのも、Rubricksが使っているComponentsの仕組みそのものがRails2で消失してしまったため。

他の数多のPluginのように外出しされたとかなら兎も角、完全に無くなってしまったのは痛い。かといって、泣き言ばっかり言ってても仕方ないので最近重い腰をあげてRails-2.1のソースを読み込んでます。

Rails2の特徴の1つとして、「セキュリティの向上」が謳われている。その内容は主に以下の2点である。

CSRF対策 sanitizeの方式がホワイトリスト方式に変更

この2点、Rubricks Projectで提供しているrails_protection pluginと完璧に被るので、まずはこの周辺から読み込んでみる。

Rails2で追加されたCSRF対策(request_forgery_protection)の特徴は以下の通りとなっている。

ランダムな文字列を自動生成し、セッションに保持する SecureRandomを利用して生成する SecureRandomが利用できない場合はMD5のダイジェスト値を利用する :secretオプションで独自に文字列を指定することが可能

上記文字列のダイジェスト値をトークンとしてform_tagで自動的に付与する 利用するハッシュアルゴリズムはSHA1 :digestオプションでハッシュアルゴリズムを指定することが可能

コントローラで宣言したアクションの処理時に上記トークンの照合を行う 例外としてGETリクエストに関しては照合を行わない





さて、ここまで読んだところでrequest_forgery_protectionには大きく3つの欠点があるように感じる。

GETリクエストをスルーしているため根本的にCSRF対策になっていない 宣言したアクションだけにしか適用されないので開発者の宣言漏れの可能性がつきまとう FORMタグを使う部分は問題ないが、JavaScriptでAJAXリクエストを発行する時は自前でトークンを付け加えてあげないといけない

１のGETリクエストをスルーする仕様に関しては、かなり致命的な問題である。例えば、以下のようなコントローラがあったとする。

==[/app/controllers/index_controller.rb]==== class IndexController < ApplicationController def index end def show end def update People.update(params[:id], params[:people]) redirect_to :action => 'index' end end ==[/config/routes.rb]==== ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' end

このようなAPに対して攻撃者が以下のような攻撃サイトを用意して、それを正規ユーザが踏めばCSRF攻撃が成立する。

Rails開発陣がどのような意図でGETリクエストをスルーする仕様にしたのか、確かなところはわからないが、Rails2ではRESTfulなAP以外を想定していないのではないかと個人的には思う。要はActiveResorce使えよ、ってことね。ActiveResorceを前提とすると先のコードは以下のようになる。





==[/app/controllers/people_controller.rb]==== class PeopleController < ApplicationController def index end def show end def update People.update(params[:id], params[:people]) redirect_to :action => 'index' end end ==[/config/routes.rb]==== ActionController::Routing::Routes.draw do |map| map.resources :people end

このようなAPの場合、#GET /people/1 で参照、 #POST /people/1 で更新となるため、GETリクエストで更新処理を試行することができない。このため「request_forgery_protection」は正常に機能し、CSRF攻撃からAPを保護することができる。

つまり、「request_forgery_protection」はRails2での基本思想である「RESTful」に準拠するAPは保護するけど、「map.connect」を使ってるようなレガシーなAPは保護しませんよーという事なのだと思う。

では、レガシーなAPをCSRF攻撃から守る機能をFW的に提供するためにはどうすればいいか。rails_protection pluginでは以下のような考えで保護機構を提供している。

更新系のリクエスト(POST/PUT/DELETE)はトークンの照合を行う セッションIDをベースにトークンを生成する 環境変数でトークン生成時に使うハッシュアルゴリズムを指定可能

参照系のリクエスト(GET/HEAD/OPTION等)はDB更新を禁止する リクエストに関わる処理でAR#saveもしくはAR#destroyが叩かれると例外を発生する GETリクエストでも更新処理が必要な場合のためにARオブジェクトにフラグをセットすることで例外の発生を回避可能





参照系のリクエストにおいて、基本的にDB更新処理を禁止することによって、上述のようなGETリクエストを利用したCSRF攻撃を回避することができる。

長くなったので２・３の論点に関しては後で書く。