fc2ブログ

2024.11 «  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - - - - » 2025.01
TOP > CATEGORY > [2.0]セッション&クッキー

TOP

Rails 2.0・その13(http only cookie をサポート)  

2008年02月26日 ()
XSS クロス・サイト・スクリプティングで問題として真っ先に上げられそうな、cookie の値を javascript で取ってくる、ということが出来ないクッキーのことです。

それを http only cookie と言ったりしますが、その設定が出来るようになりました。

options = { "name" => "quality", "value" => "owesome", "http_only" => true }
cookie = CGI::Cookie.new(options)



http only クッキーとはなんぞや、の方は、

T.Teradaの日記
Mitigating Cross-site Scripting With HTTP-only Cookiesがお勧めです。

【広告】

[2008.02.26(Tue) 02:18] [2.0]セッション&クッキーTrackback(0) | Comments(0) 見る▼
↑TOPへ

Rails 2.0・その12(CSRFを勝手に防止)  

2008年02月22日 ()
CSRF (Cross-Site Request Forgery) を勝手に防止してくださいます。

CSRF とは簡単に言うと、ある特定のURLがDBに挿入したり更新したりすると仮定します。そして、そのURLにアクセスしまくってDBの値を変えまくることです(だと思う・・・)。

script/generate scaffold した時点で、もう既に対応済みになっていて何もすることはありませんでした。

じゃあ、どこで設定されているか、というと、app/controllers/application.rb をご覧ください。

  protect_from_forgery # :secret => '6d0b20bbf9203508337aff3214f79efb7a0'



protect_from_forgery と書くだけです。終わり。

そうすると、POST・DELETE・PUTの時(つまりGETじゃあないとき)に、Rails が、authenticity_tokenという hidden パラメータのようなものを付け加えてくれてこれでチェックされます。

Rails がチェックした時に、不正なアクセスだったら、ActionController::InvalidAuthenticityTokenという例外が発生します。

とここまでが概要です。いかがでしたでしょうか。以下の行からは、もうちょっと掘り下げて書いてみようかなと思います。といっても期待はしないでねと。


さてさて、上記の protect_from_forgery の箇所で、:secret から後ろがコメントアウトされていました。

クッキーセッション(Rails2.0.2ではデフォルトざます)を使っている時は、このコメントを外さないでください。

DBセッションやメモリセッションを使っている時は、このコメントを外してください。というか、:secret の値を書き換えて、さらにその値が外部のスパイにばれないようにしてください。

じゃあなんでコメントアウト外したり外さなかったりなの?とか、そもそもCSRF防止になるの?とかは、以下に更に掘り下げて書いてみたいと思いますが、超長くなるので、はっきり言って飽きるかもです。


1.デフォルトであるクッキーセッションを使っている場合

CSRF されているかどうかは、

クライアントの session[:csrf_id] から生成されたチェックサム

クライアントから送られてきた authenticity_token

が同じかどうかで判断します。

actionpack-2.0.2/lib/action_controller/request_forgery_protection.rb

      def verified_request?
        !protect_against_forgery? ||
          request.method == :get ||
          !verifiable_request_format? ||
          form_authenticity_token == params[request_forgery_protection_token]
      end



      def form_authenticity_token
        @form_authenticity_token ||= if request_forgery_protection_options[:secret]
          authenticity_token_from_session_id
        elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
          authenticity_token_from_cookie_session
        elsif session.nil?
          raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
        else
          raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
        end
      end



      def authenticity_token_from_cookie_session
        session[:csrf_id] ||= CGI::Session.generate_unique_id
        session.dbman.generate_digest(session[:csrf_id])
      end



ちなみに、authenticity_token を作るときには、上記の form_authenticity_token を呼びます。

つまり、自前で authenticity_token=<%= form_authenticity_token %> 的なことができます。

なので、結局の所、クライアントの session[:csrf_id] から param[:authenticity_token] と同じ値を作ることができるかどうかを見ていると言っていいでしょう。

じゃあ、改ざんは?できるんじゃあないの?と思いますが、まず、session[:csrf_id] を改ざんしたいといった場合は、セッションのチェックサムも対応する値に変えなくてはいけません。

どういうことかというと、クッキーセッションの場合はブラウザのクッキーに

セッションの値--セッションの値から生成したチェックサム

という長ーい文字列が設定されています。

session/cookie_store.rb

  def close
    if defined?(@data) && [email protected]?
      updated = marshal(@data)
      raise CookieOverflow if updated.size > MAX
      write_cookie('value' => updated) unless updated == @original
    end
  end



  def generate_digest(data)
    key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
    OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
  end



    def marshal(session)
      data = Base64.encode64(Marshal.dump(session)).chop
      CGI.escape "#{data}--#{generate_digest(data)}"
    end



この「セッションの値」を変える(session[:csrf_id]の値をいじくる)ということは、「セッションの値から生成したチェックサム」も併せて変えなければ Rails が有効なクッキーとして扱ってくれません。

session/cookie_store.rb

  def restore
    @original = read_cookie
    @data = unmarshal(@original) || {}
  end



    def unmarshal(cookie)
      if cookie
        data, digest = CGI.unescape(cookie).split('--')
        unless digest == generate_digest(data)
          delete
          raise TamperedWithCookie
        end

        Marshal.load(Base64.decode64(data))
      end
    end



でもこのチェックサム、サーバ側に書いてある文字列がないと生成できないようになっています。

なので、勝手にセッションの値は書き換えられません。

サーバ側に書いてある文字列っていうのが以下 :secret の値です。

config/environment.rb

config.action_controller.session = {
    :session_key => '_scaf2_session',
    :secret => 'c0b6ae4957515a57bb58d880150ecdd71faa8990ff0d7ff35635cd55'
}




じゃあ、authenticity_token は改ざんできるか、ということですが、これは、もともと session[:csrf_id] から作られたものですから、session[:csrf_id] の値を何に変えたらいいのか分かりませんし、「セッションの値から生成したチェックサム」の値も変えなくてはいけませんから、それはできない、ということで無理なのでした。


ここまで、すごく長いなあ。誰もこのエントリ読まなさそうだなあorz


2.デフォルトであるクッキーセッションを使っていない場合

CSRF されているかどうかは、

クライアントのセッションIDとprotect_from_forgeryのオプション値:secretとのチェックサム

クライアントから送られてきた authenticity_token

が同じかどうかで判断します。

actionpack-2.0.2/lib/action_controller/request_forgery_protection.rb

      def verified_request?
        !protect_against_forgery? ||
          request.method == :get ||
          !verifiable_request_format? ||
          form_authenticity_token == params[request_forgery_protection_token]
      end



      def form_authenticity_token
        @form_authenticity_token ||= if request_forgery_protection_options[:secret]
          authenticity_token_from_session_id
        elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
          authenticity_token_from_cookie_session
        elsif session.nil?
          raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
        else
          raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
        end
      end



      def authenticity_token_from_session_id
        key = if request_forgery_protection_options[:secret].respond_to?(:call)
          request_forgery_protection_options[:secret].call(@session)
        else
          request_forgery_protection_options[:secret]
        end
        digest = request_forgery_protection_options[:digest] ||= 'SHA1'
        OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
      end



基本的に、クライアントのクッキーにセッションIDが設定されていて、サーバ側にはセッションIDに紐付いたsession情報がファイルやDBやメモリに格納されています。

チェックサム(authenticity_token)は、protect_from_forgery のオプションに指定されている :secret の値と、ブラウザから渡されたセッションIDから上記の authenticity_token_from_session_id のように生成されます。

以下は、protect_from_forgery のオプションに:secretを指定している、の所です。

app/controllers/application.rb

  protect_from_forgery :secret => '6d0b20bbf9203508337aff3214f79efb7a0'



セッションIDが同じであれば、authenticity_token もずーっと一緒なのですね。

この場合の改ざんの心配なのですが、セッションIDを改ざんするとauthenticity_tokenも変えなきゃならないですが、:secretの値が分からないので無理ですね。

authenticity_tokenの値を改ざんすると、セッションIDも変えなきゃならなくて、:secretの値が分からないので無理ですね。



ということでした。いやぁ、書くの大変だったけどこの情報が役に立ったという人が世に1人出ればOKにしておきます。

【広告】

[2008.02.22(Fri) 02:22] [2.0]セッション&クッキーTrackback(0) | Comments(11) 見る▼
↑TOPへ


COMMENT

by とおりがかり
ちょっとはまってたので勉強になりましたー

Re: タイトルなし by 自分
はまり状態から抜け出せたようで良かったですね。
コメントが嬉しかったのでもうそろそろ記事の更新を久しぶりにしてみよーかなー?

by 通りすがり
>いやぁ、書くの大変だったけどこの情報が役に立ったという人が世に1人出ればOKにしておきます。
謎のエラーで困ってました.
役に立ちました!!
ありがとうございます.

良かった by 自分
このエントリー、ブログの中でも1,2を争うぐらい書くのが大変だったのでこういう風に言っていただけると報われます!

by -
参考になりました!
ありがとう!!

どういたしまして by 自分
参考になってなによりです!
rubyは個人的にはソースコードが読みやすいので好きです!

by -
じっくり拝見させて頂きました。
すごく勉強になりました!
ありがとうございます。

いえいえこちらこそ by 自分
また良かったらのぞきにきてください。
この記事、勢いで書ききった記事です。
記事を書いた次の日に、この記事を自分で読み返してみて、ふむふむ、すごい、なるほど、と早速自分で書いた内容を忘れていて思い出すことになる始末。
でもどうやら世の中の1人以上の方の役に立っているようなのでなんだかマンモスうれピーです(っていう語彙は今使うとブラックな表現になっちゃうのかな?)

参考になりました by -
これから Ruby On Rails を学ぼうとしている者です。
始めるにあたり、この言語ではセキュリティ対策ってどうやるんだろう、CSRFを防ぐにはどうすればいいんだろう、と思っていたのですが、こんなにあっさりしているんですね。
ますます使ってみようと思いました。執筆おつかれさまでした。

あっさりしてます。 by 自分
あっさり味のruby on rails.
もしかしてそのうち ror.start と一行書くだけでWebサイトが動作してしまうぐらい進化してしまうかもしれませんんね。

コメントを閉じる▲

Rails 2.0・その11(セッションの情報は全てクライアントのクッキーに書かれる)  

2008年02月20日 ()
Rails 2.0 がおっしゃいますに、結局セッションに入れる情報ってユーザーIDだけぐらいで短いから、セッションの情報は全部クッキーに入れちゃえばいいんじゃね?みたいに割り切っているようです。

例としては、コントローラ辺りに、session[:food] = 'nikkorogashi'

と書いて、そこの処理を通りますと、session オブジェクトが文字列化(Marshal)されてBase64エンコードされてクッキーに保存されます。

要するにクッキーに保存しやすい文字列に変換されます。

決して暗号化されて保存されるわけではありません。

やる気なら誰でも自分のクッキーの情報を見て、session にどういう値が入っているのかを解析できるので、大切な情報(クレジットカード番号とかパスワードとかスリーサイズとか)は session には入れない方がいいです。

どうやってこのようにクッキーを解読するのか、はこちらのブログがいいと思いましたので参考にしてみてください。

じゃあ、クッキーじゃない場所にセッションの情報を保存したいんですけど、みたいな場合はどうするか、というと、

config/environment.rb に

config.action_controller.session_store = クッキー保存オプション


を書いてください。クッキー保存オプションは、

:active_record_store, :p_store, drb_store, :mem_cache_store, :memory_store を選べます。

それぞれのオプションを使うときの注意点はこちらのサイトこちらのサイトなどを参考にしてみてください。

active_record_store については、私のエントリも役に立つかもしれません。

【広告】

[2008.02.20(Wed) 02:01] [2.0]セッション&クッキーTrackback(0) | Comments(0) 見る▼
↑TOPへ

TOP