Rails 3.2.3 での CSRF 対策について調べていて、ちょっとはまったポイントがあったのでメモを残しておきます。
Rails はデフォルトで特に何もしなくても POST/PUT/DELETE のリクエストに対して authenticity_token という hidden のパラメーターを利用して CSRF 対策をおこなってくれる仕組みを持っている。(Ruby On Rails ピチカート街道 - Rails 2.0・その12(CSRFを勝手に防止) - などが参考になる)
しかしそれは Rails の form_for などのヘルパーを使った場合で、form タグを自分で書いた場合には、
のように手動で authenticity_token パラメーターを作って POST のパラメーターとして送る必要がある。
authenticity_token を送らない場合の挙動を確認したくて、適当に scaffold で model/view/controller 一式を作成し、form_for ヘルパーを使っている部分を以下のようにベタな html に書きなおし、あえて authenticity_token を送らないようにする。
これで、POST のリクエストをおこなってもエラーとなって、新しいデータは作成されないと思ったのだが、予想に反してデータは作成されてしまう。
あれ、これだと CSRF 対策にならないじゃないか、と思いいろいろと試行錯誤。
ログをみると authenticity_token を送らない場合は
WARNING: Can't verify CSRF token authenticity
という WARNING のメッセージは確かに出ている。でも POST のリクエストは通ってしまう。
Rails 2.x を触っていたころの記憶では、authenticity_token を送ってない場合にエラーで悩まされた覚えがある。
で、しばしはまった後にわかったのは、authenticity_token を送らない場合は、POST のリクエストは通るのだが session がリセットされているということだった。
devise などで何らかのユーザー認証の仕組みをいれている場合に、POST/PUT/DELETE のアクションに対して、session に格納されているユーザー情報の値でもって認証をかけるのが普通だ。
これを scaffold でつくった簡単なフォームで擬似的に実現しようとすると、
のように session を確認する authorize メソッドを定義しておいて、PeopleController では、
のように作成、変更、削除のアクションに対して authorize メソッドを before_filter でかける。
authenticity_token パラメーターを送らなかったり、値が違っている場合は session がリセットされるので、リクエスト自体は通っても authorize で蹴られてデータは作成されない。
「うーん、でもおかしいなあ、以前は確かにエラーではじかれた覚えがあるんだよなあ」と思って、もうちょっと調べていたら、Ruby on Rails Guides: Ruby On Rails Security Guideに
とちゃんと書いてありました。
ドキュメントをきちんと読んでなかった僕が悪い。
Rails はデフォルトで特に何もしなくても POST/PUT/DELETE のリクエストに対して authenticity_token という hidden のパラメーターを利用して CSRF 対策をおこなってくれる仕組みを持っている。(Ruby On Rails ピチカート街道 - Rails 2.0・その12(CSRFを勝手に防止) - などが参考になる)
しかしそれは Rails の form_for などのヘルパーを使った場合で、form タグを自分で書いた場合には、
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
のように手動で authenticity_token パラメーターを作って POST のパラメーターとして送る必要がある。
authenticity_token を送らない場合の挙動を確認したくて、適当に scaffold で model/view/controller 一式を作成し、form_for ヘルパーを使っている部分を以下のようにベタな html に書きなおし、あえて authenticity_token を送らないようにする。
<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post"> <div class="field">
<label for="person_name">Name <input id="person_name" name="person[name]" size="30" type="text" />
</div>
<div class="actions">
<input name="commit" type="submit" value="Create Person" />
</div>
</form>
これで、POST のリクエストをおこなってもエラーとなって、新しいデータは作成されないと思ったのだが、予想に反してデータは作成されてしまう。
あれ、これだと CSRF 対策にならないじゃないか、と思いいろいろと試行錯誤。
ログをみると authenticity_token を送らない場合は
WARNING: Can't verify CSRF token authenticity
という WARNING のメッセージは確かに出ている。でも POST のリクエストは通ってしまう。
Rails 2.x を触っていたころの記憶では、authenticity_token を送ってない場合にエラーで悩まされた覚えがある。
で、しばしはまった後にわかったのは、authenticity_token を送らない場合は、POST のリクエストは通るのだが session がリセットされているということだった。
devise などで何らかのユーザー認証の仕組みをいれている場合に、POST/PUT/DELETE のアクションに対して、session に格納されているユーザー情報の値でもって認証をかけるのが普通だ。
これを scaffold でつくった簡単なフォームで擬似的に実現しようとすると、
class ApplicationController < ActionController::Base
protect_from_forgery
def authorize
unless session[:login]
redirect_to root_url, :notice => "not authorized"
end
end
end
のように session を確認する authorize メソッドを定義しておいて、PeopleController では、
before_filter :authorize, :only => [:create, :update, :destroy]
のように作成、変更、削除のアクションに対して authorize メソッドを before_filter でかける。
authenticity_token パラメーターを送らなかったり、値が違っている場合は session がリセットされるので、リクエスト自体は通っても authorize で蹴られてデータは作成されない。
「うーん、でもおかしいなあ、以前は確かにエラーではじかれた覚えがあるんだよなあ」と思って、もうちょっと調べていたら、Ruby on Rails Guides: Ruby On Rails Security Guideに
If the security token doesn’t match what was expected, the session will be reset. Note: In Rails versions prior to 3.0.4, this raised an ActionController::InvalidAuthenticityToken error.
security token が期待された値とマッチしない場合はセッションがリセットされる。Rails 3.0.4 以前は ActionController::InvalidAuthenticityToken error を出していた。
とちゃんと書いてありました。
ドキュメントをきちんと読んでなかった僕が悪い。
2012/11/28 14:43:31