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 > Rails 2.0・その12(CSRFを勝手に防止)

 ← Rails 2.0・その13(http only cookie をサポート) | TOP | Rails 2.0・その11(セッションの情報は全てクライアントのクッキーに書かれる)

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サイトが動作してしまうぐらい進化してしまうかもしれませんんね。

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

コメントを閉じる▲
 ← Rails 2.0・その13(http only cookie をサポート) | TOP | Rails 2.0・その11(セッションの情報は全てクライアントのクッキーに書かれる)

COMMENT

ちょっとはまってたので勉強になりましたー
[ 2009.03.13(Fri) 14:11] URL | とおりがかり #- | EDIT |

はまり状態から抜け出せたようで良かったですね。
コメントが嬉しかったのでもうそろそろ記事の更新を久しぶりにしてみよーかなー?
[ 2009.03.16(Mon) 08:52] URL | 自分 #RaJW5m0Q | EDIT |

>いやぁ、書くの大変だったけどこの情報が役に立ったという人が世に1人出ればOKにしておきます。
謎のエラーで困ってました.
役に立ちました!!
ありがとうございます.
[ 2009.05.11(Mon) 22:54] URL | 通りすがり #XIX18EW2 | EDIT |

このエントリー、ブログの中でも1,2を争うぐらい書くのが大変だったのでこういう風に言っていただけると報われます!
[ 2009.05.12(Tue) 11:44] URL | 自分 #RaJW5m0Q | EDIT |

参考になりました!
ありがとう!!
[ 2009.06.17(Wed) 16:28] URL | #- | EDIT |

参考になってなによりです!
rubyは個人的にはソースコードが読みやすいので好きです!
[ 2009.06.18(Thu) 14:06] URL | 自分 #RaJW5m0Q | EDIT |

じっくり拝見させて頂きました。
すごく勉強になりました!
ありがとうございます。
[ 2009.11.04(Wed) 11:57] URL | #v9z76/as | EDIT |

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

これから Ruby On Rails を学ぼうとしている者です。
始めるにあたり、この言語ではセキュリティ対策ってどうやるんだろう、CSRFを防ぐにはどうすればいいんだろう、と思っていたのですが、こんなにあっさりしているんですね。
ますます使ってみようと思いました。執筆おつかれさまでした。
[ 2010.10.08(Fri) 16:21] URL | #- | EDIT |

あっさり味のruby on rails.
もしかしてそのうち ror.start と一行書くだけでWebサイトが動作してしまうぐらい進化してしまうかもしれませんんね。
[ 2010.10.08(Fri) 17:37] URL | 自分 #RaJW5m0Q | EDIT |

参考になりました。ありがとう。
[ 2012.11.09(Fri) 17:21] URL | #- | EDIT |

COMMENT POST















管理者にだけ表示

 ← Rails 2.0・その13(http only cookie をサポート) | TOP | Rails 2.0・その11(セッションの情報は全てクライアントのクッキーに書かれる)