こんにちは、鈴木です。
CSRF 対策で使用する protect_from_forgery ですが、Rails3 からはトークンの検証 NG の場合に reset_session するように動作が変更されました。
今まではトークンの検証で NG だった場合に InvalidAuthenticityToken が raise されていましたが、デフォルトでは reset_session されるようになりました。
protect_from_forgery の動作
protect_from_forgery は、以下のようにコントローラに書いておくことで、リクエストとセッションに持たせたトークンが等しいことを検証してくれます。
1 2 3 4 5 |
class ApplicationController < ActionController::Base protect_from_forgery end |
トークンの検証で NG となった場合の動作ですが、 Rails2 と Rails3 で以下のように異なります。
- Rails2 ... InvalidAuthenticityToken が raise される。
- Rails3 ... reset_session が実行される。
InvalidAuthenticityToken を raise するように戻す
ソースコードを追いかけたところ、トークンの検証が NG の場合の処理を行なっている場所が見つかりました。
lib/action_controller/metal/request_forgery_protection.rb で定義されている handle_unverified_request です。
1 2 3 4 5 |
# This is the method that defines the application behavior when a request is found to be unverified. # By default, \Rails resets the session when it finds an unverified request. def handle_unverified_request reset_session end |
コメントに書かれていますが、handle_unverified_request はトークンの検証が NG だった場合の挙動を決めるメソッドであり、デフォルトではセッションをリセットします。
以前のように InvalidAuthenticityToken を raise するように戻したい場合は、ApplicationController などで handle_unverified_request を上書きすれば OK です。
1 2 3 4 5 6 7 8 9 10 11 |
class ApplicationController < ActionController::Base protect_from_forgery protected def handle_unverified_request raise ActionController::InvalidAuthenticityToken end end |
これで以前のように、トークンの検証が NG の場合に InvalidAuthentyicityToken が raise されるようになりました。
あとがき
protect_from_forgery の動作が変わっていることに気付いた時に、とんでもない勘違いをしてしまいました。
「ログイン不要なページでは reset_session しても CSRF できてしまうじゃないか!」と。
これは単なる勘違いで、ログインしていないのであれば CSRF ではありません。
例えば、ショッピングサイトにログインしているとすると、
- Cookie にログイン情報が保持されている。
- ショッピングサイトのページを閲覧するたびに Cookie が送信される(Cookie には攻撃サイトには推測困難なセッション ID が含まれる)。
- 攻撃サイトはページが表示された時に、バレないようにショッピングサイトの注文確定ページに POST する。
- 攻撃サイトが用意した「注文する」という情報とともに、ブラウザからは Cookie も送信されるので、ログインしている「あなた」が注文を確定したことになる。
というものが CSRF です。
つまり、攻撃サイトが用意した「注文する」という情報と、ショッピングサイトにアクセスするたびにブラウザから自動送信される「あなた」を特定する情報の組み合わせで、「あなた」が注文したことになる。
これが CSRF です。
ログインしていなければ、ブラウザから自動送信される「あなた」を特定する情報は無いので、CSRF ではありません。
CSRF を理解していると思っていたものの、きちんと理解できていなかったので勘違いしてしまいました。
セキュリティ関係の話は、「聞いたことがある」では不十分、「だいたい理解している」でも不十分、ということを身にしみた出来事でした。