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>某所でセッションデータは引き継ぎたいという話があったのでちょっと改良。