質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Q&A

1回答

2203閲覧

Rails4.2で複数モデルを一括登録しようとするとCSRF対策に引っ掛かる。というかパラメータが吹き飛んでいる。

17number

総合スコア14

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

0グッド

1クリップ

投稿2017/02/23 04:09

編集2017/02/24 07:38

###前提
####作ろうとしているもの

  • RailsでWebアプリを開発中
  • User has_many Hobbies / Hobby belongs_to UserHobby モデルを 1画面(1フォーム)で複数まとめて追加したい

####開発環境

  • OS : Ubuntu 14.04.5 64bit(vagrant環境)
  • Ruby : 2.3.1
  • Rails : 4.2.7.1
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=14.04 DISTRIB_CODENAME=trusty DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS" $ uname -a Linux vagrant-ubuntu-trusty-64 3.13.0-100-generic #147-Ubuntu SMP Tue Oct 18 16:48:51 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $ ruby -v ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux] $ rails -v Rails 4.2.7.1

####開発経験・スキル
Rails経験は3ヶ月程度、実務(委託や自社サービスという意味)レベルでの経験はなく、Web系の開発経験も無し、個人で開発をしている程度のレベルです。そのレベルの人間の質問なので、おかしなことを言っていれば何でもご指摘頂けると助かります。

###質問内容
今回起きたトラブルに対して色んな調査をしたため、本文がきれいにまとまっておらず申し訳ありません。長文となっていますが、なにかお気付きの点やアドバイスなどあれば、コメント頂けると幸いです。また、teratail初利用なので作法など守れていない点があるかもありません。

2017/02/24 16:36追記
検証に用いたコードをgithubに置いたので、コード中に気になる点などありましたら、ご指摘ください。
https://github.com/17number/rails-multiform-csrf-error

以下、質問本文になります。


User に紐づく Hobby を、1画面で3つ追加しようとした。参考にしたページは以下。

Rails4、fields_forを使って一括更新する処理のベストプラクティスは何だろう | もふもふ技術部

具体的なソースコードは以下、あまり関係なさそうな部分は割愛。

ruby

1#app/models/user.rb 2class User < ActiveRecord::Base 3 has_many :hobbies 4end 5 6#app/models/hobby.rb 7class Hobby < ActiveRecord::Base 8 belongs_to :user 9end 10 11#app/views/hobbies/add_hobbies.html.slim 12= form_tag commit_user_hobbies_path, method: :put do 13 - @hobbies.each do |hobby| 14 = fields_for "hobbies[]", hobby do |hf| 15 = hf.label :name 16 = hf.text_field :name 17 br 18 .actions = submit_tag 19 20#app/controllers/hobbies_controller.rb 21class HobbiesController < ApplicationController 22 before_action :set_hobby, only: [:show, :edit, :update, :destroy] 23 before_action :set_user, only: [:index, :new, :create, :edit, :update, :add_hobbies, :commit_hobbies] 24 25 # GET /hobbies/multiadd 26 def add_hobbies 27 # 合計3つのHobbiesを追加したい 28 (3 - @user.hobbies.length).times {@user.hobbies.build} 29 @hobbies = @user.hobbies 30 end 31 32 # PUT /hobbies/multiadd 33 def commit_hobbies 34 # そもそも上手く動かないので具体的な処理は未実装 35 binding.pry 36 redirect_to user_hobbies_path(@user) 37 end 38 39 private 40 # Use callbacks to share common setup or constraints between actions. 41 def set_hobby 42 @hobby = Hobby.find(params[:id]) 43 end 44 45 def set_user 46 @user = User.find(params[:user_id]) 47 end 48end 49 50#config/routes.rb 51Rails.application.routes.draw do 52 resources :users do 53 resources :hobbies do 54 collection do 55 get "add_hobbies" => "hobbies#add_hobbies", as: :add, path: :multiadd 56 match "add_hobbies" => "hobbies#commit_hobbies", as: :commit, path: :multiadd, via: [:post, :put] 57 end 58 end 59 end 60end 61 62#db/schema.rb 63ActiveRecord::Schema.define(version: 20170223020954) do 64 create_table "hobbies", force: :cascade do |t| 65 t.integer "user_id", limit: 4 66 t.string "name", limit: 255 67 end 68 add_index "hobbies", ["user_id"], name: "index_hobbies_on_user_id", using: :btree 69 70 create_table "users", force: :cascade do |t| 71 t.string "name", limit: 255 72 end 73 add_foreign_key "hobbies", "users" 74end

####なぜかCSRF対策に引っ掛かる
このフォームを使った時、特定条件においてCSRF対策に引っ掛かる(Can't verify CSRF token authenticity)。
現状わかっている条件は以下の通り。

  • あらかじめ1つ、もしくは2つの Hobby が登録されている状態。つまり、見た目的には複数フォームの内、1つか2つのフォームがあらかじめ埋まっている状態。

なので、画面アクセス時の初期状態が以下であれば、CSRF対策には引っ掛かっていない。

  • 3つあるフォームが全て空っぽ
  • 3つあるフォームが全て埋まっている(あらかじめ3つの Hobby が登録されている)

#####CSRF対策に引っ掛かる原因調査
色々と調べた結果、以下のページに辿り着いたのでもう少し調べてみた。

RailsのCSRF対策の仕組みについて - Programming log - Shindo200
Rails4のCSRF対策で「Can't verify CSRF token authenticity」エラー - Qiita

######verified_request?メソッドはパスできてるか?
verified_request? メソッドに binding.pry を仕込んでみたところ、本来ならパスするはずの以下をパスできていない。(※上手くいくケースは当該条件をパスしている事は確認済)

valid_authenticity_token?(session, form_authenticity_param)

form_authenticity_param = nil となっているのが原因。なお、form_authenticity_paramparams から Token を取ってくるだけの模様。

ruby

1# actionpack-4.2.7.1/lib/action_controller/metal/request_forgery_protection.rb 2# The form's authenticity parameter. Override to provide your own. 3def form_authenticity_param 4 params[request_forgery_protection_token] 5end

######Tokenはフォームに埋まっているか
そもそも Tokenform_tagform_for を使えば自動で埋められるということで、当該画面のHTMLソースコードを確認したところ、 Token は埋まっていた。

######protect_from_forgeryを使ってみたが根本解決せず
参考ページを見て、protect_from_forgery :except => [:commit_hobbies]を追加してみたところ、CSRF対策(Can't verify CSRF token authenticity)はパスできるようになった。

が、params の中身がスカスカになってしまっているため、期待動作(3つの Hobbies を登録する)は実現できなかった。

ruby

1#上手くいくケース(これはAll blank状態からフォーム利用) 2Rails4.2.7.1 Ruby2.3.1 pry(#<HobbiesController>)> params 3=> {"utf8"=>"?", 4 "_method"=>"put", 5 "authenticity_token"=>"E4Yb/VmuFIi+q4+mdcZ4qoTyDBubl0Wjdz9hxTJ/+cHhkrt7cg2U+aim8l3r0CIyYUoMjs1phfwSz8CTALfLFA==", 6 "hobbies"=>[{"name"=>"a"}, {"name"=>"b"}, {"name"=>"c"}], 7 "commit"=>"Save changes", 8 "controller"=>"hobbies", 9 "action"=>"commit_hobbies", 10 "user_id"=>"2"} 11 12#ダメなケース(フォームが1つ埋まっている状態からフォーム利用) 13Rails4.2.7.1 Ruby2.3.1 pry(#<HobbiesController>)> params 14=> {"controller"=>"hobbies", 15 "action"=>"commit_hobbies", 16 "user_id"=>"2"}

####そもそもコンソール上に表示されているParametersがおかしいっぽい
色々と見ていると、コンソール上の表示からしておかしい。authenticity_token など色々と足りていない模様。

# 上手くいくケース Started PUT "/users/2/hobbies/multiadd" for 10.0.2.2 at 2017-02-23 12:48:46 +0900 Processing by HobbiesController#commit_hobbies as HTML Parameters: {"utf8"=>"?", "authenticity_token"=>"E4Yb/VmuFIi+q4+mdcZ4qoTyDBubl0Wjdz9hxTJ/+cHhkrt7cg2U+aim8l3r0CIyYUoMjs1phfwSz8CTALfLFA==", "hobbies"=>[{"name"=>"a"}, {"name"=>"b"}, {"name"=>"c"}], "commit"=>"Save changes", "user_id"=>"2"} # ダメなケース(protect_from_forgery の有無に関わらずこんな感じ) Started POST "/users/2/hobbies/multiadd" for 10.0.2.2 at 2017-02-23 12:50:38 +0900 Processing by HobbiesController#commit_hobbies as HTML Parameters: {"user_id"=>"2"} User Load (1.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1

あらためて確認したところ、protect_from_forgery を追加しようがしまいが、上記のような内容だった。そもそも Token が含まれていないため、CSRF対策に引っ掛かっていたっぽい。

#####パケットキャプチャ上は正常に見える
じゃあ、Token はどこで消えてるのかと思い、開発環境上でパケットキャプチャも取得してみた。パケット中には Tokenhobbies 相当のデータも入っている事を確認した。

####その他 試したこと

#####form_tagmethod 変更
色々とググっていく内に form_tagmethod を変更したら状況変わるかも、と思い試してみました。が、結果としては同じでした。

試したケース

  • method=post(default)
  • method=put

####現状わかっていること
Rails内のどこかで何かの条件を満たせずに(or 満たしてしまい)、必要なパラメータ群が吹っ飛んでいるっぽい、というとこまでは分かったが、スキルと時間が足りないのでこの場で質問させてもらっています。

  • 上手くいかない原因は何なのか
  • どこを変更すれば良い
  • そもそもコードが間違っている

など、なにかコメント頂けると助かります。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryochin

2017/02/24 07:08

斜め読みですが、上手くいくケースは PUT でダメなケースは POST なのはなぜですか?
17number

2017/02/24 07:23

コメント、ありがとうございます。ご指摘頂いた method の違いについては気付いていませんでした。ただ、意図してそうなったものではなく、何故かそうなっていました。コメント頂いてから思い出したのですが、form_tag の method 変更も試したりもしました(が、変更してもしなくても結果は同じでした)。このログ自体は質問内容のソースで取得しているため、method 変更前と変更後の結果が混ざっている訳ではありません。念のため、試したこととして質問を修正しておきます。
guest

回答1

0

PHPですが、CSRF用のTokenでハマった例を。ご参考になれば幸いです。

  • ファイル選択フォームがあったため、methodがPUTとして扱われた
    ファイル選択フォームがあったため、enctypeに"multipart/form-data"としていた

結果、$_POSTでなく$_FILESにTokenが入ってしまっていた

  • 設置したAjaxがサーバ側のTokenを更新してしまっていた
  • javascriptがTokenを消してしまっていた
  • そもそもformタグの中にTokenフォームが入っていなかった

投稿2017/03/15 02:27

clickmaker

総合スコア200

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問