2005-07-15 (Fri) [長年日記]
_ CSRF対策
0.13.0にもCSRF対策のコードはとくにないようだ。 MLの議論を追ってなかったのだが、結局アプリケーション側で対策する べしということだろうか。
まず、ApplicationControllerとApplicationHelperに以下のような記述をしておく。
app/controller/application.rb:
class ApplicationController < ActionController::Base private def validate_session if @params[:session_id_validation] == @session.session_id return true else render(:text => SESSION_VALIDATION_FAILED_HTML, :status => "403 Forbidden") return false end end SESSION_VALIDATION_FAILED_HTML = <<EOF <html> <head> <title>403 Forbidden</title> </head> <body> <h1>403 Forbidden</h1> <p> Session validation failed. </p> </body> </html> EOF end
app/helpers/application_helper.rb:
module ApplicationHelper def secure_form_tag(*args) return start_form_tag(*args) + "\n" + hidden_field_tag("session_id_validation", @session.session_id) end end
あとは、保護が必要な各フォームでstart_form_tagの代りにsecure_form_tagを 使い、
app/views/<controller_name>/edit.rhtml:
<h1>Edit</h1> <%= secure_form_tag :action => 'update', :id => @model_name %> <%= render_partial 'form' %> <%= submit_tag 'Edit' %> <%= end_form_tag %> <%= link_to 'Show', :action => 'show', :id => @model_name %> | <%= link_to 'Back', :action => 'list' %>
各コントローラの必要なアクションにフィルタを設定する。
app/controllers/<controller_name>_controller.rb
class <ControllerName>Controller < ApplicationController before_filter :validate_session, :only => [:create, :update, :destroy] #... end
こんなもんかなあ。
あと、scaffoldはGETでdestoryするようなコードを出力するので、 確認画面を用意してPOSTでdestroyするようにする必要がある。
_ Login GeneratorのSession Fixation Attack対策
今度はSession Fixation Attack(セッション固定攻撃)対策。
RailsはデフォルトでセッションIDを発行するようになっているが、 Login Generatorが生成するコードは、どうも認証後も同じセッションID を使っているようだ。
def login case @request.method when :post if @session[:user] = User.authenticate(@params[:user_login], @params[:user_password]) flash['notice'] = "Login successful" redirect_back_or_default :action => "welcome" else flash.now['notice'] = "Login unsuccessful" @login = @params[:user_login] end end end
このため、Session Fixation Attackの危険性があると思われる。
これを回避するには以下のようにセッションをリセットしてやればよい。
def login case @request.method when :post user = User.authenticate(@params[:user_login], @params[:user_password]) if user return_to = @session[:return_to] @request.reset_session @session = @request.session @session[:user] = user @session[:return_to] = return_to if return_to flash['notice'] = "Login successful" redirect_back_or_default :controller => "page", :action => "list" else flash.now['notice'] = "Login unsuccessful" @login = @params[:user_login] end end end
_ クッキーのパス
Railsではクッキーのパスはデフォルトで/に設定されるようだ。 これは/以外の場所(たとえば/ximapd)で運用する場合は望ましくない。
変更するには以下のようにconfig/environment.rbの末尾に記述すればよい。
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_path] = "/ximapd"
_ セッションファイルの作成場所
Railsではセッションファイルの作成場所はデフォルトで/tmpになる。 *1
/tmpの直下にセッションファイルを作るのはあまり好ましくない。 なぜなら、cronなどで削除した際に、同じファイル名で偽物のセッションファイルを作成できてしまうからだ。
変更するには以下のようにconfig/environment.rbの末尾に記述すればよい。
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:tmpdir] = File.expand_path("session", RAILS_ROOT)
もちろん、ディレクトリのパーミッションには注意が必要。 現在のCGI::Sessionでは、ファイル名からセッションIDを推測できないように なっているが、Railsの実行ユーザ以外はアクセスできないようにしておいた方が いいだろう。
*1 実際には環境による。
http://shugo.net/jit/20050716.html#p01<br>Journal InTime<br>[Ruby] Login GeneratorのSession Fixation Attack対策<br><br>某所でセッションデータは引き継ぎたいという話があったのでちょっと改良。