トップ «前の日記(2005-07-14 (Thu)) 最新 次の日記(2005-07-16 (Sat))» 編集   RSS 1.0 FEED  

Journal InTime


2005-07-15 (Fri) [長年日記]

_ [Rails] 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するようにする必要がある。

_ [Rails] 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] クッキーのパス

Railsではクッキーのパスはデフォルトで/に設定されるようだ。 これは/以外の場所(たとえば/ximapd)で運用する場合は望ましくない。

変更するには以下のようにconfig/environment.rbの末尾に記述すればよい。

ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_path] = "/ximapd"

_ [Rails] セッションファイルの作成場所

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  実際には環境による。

本日のツッコミ(全1件) [ツッコミを入れる]
_ TrackBack (2005-07-16 (Sat) 17:54)

http://shugo.net/jit/20050716.html#p01<br>Journal InTime<br>[Ruby] Login GeneratorのSession Fixation Attack対策<br><br>某所でセッションデータは引き継ぎたいという話があったのでちょっと改良。