Railsでのセキュリティリスクとその対策
代表的なセキュリティ攻撃とRailsでの対策を復習
CRSF(クロスサイトリクエストフォージェリ)
ログインした状態(セッションIDを維持した状態)を悪用してリクエストすること
ログイン後の利用者のみが利用可能なサービスを悪用したり(不正送金や商品購入、退会)、編集可能な情報を改竄できてしまう。
ログインした状態で不正なリンクをクリック。Webサイトを移り(クロス)、ユーザーの要求を偽って(フォージェリ)、悪意のあるリクエストが送信される
対策
GET/POSTを正しく使うこと
GETは情報読み出し、POSTは状態変化や何らかの作用を伴う操作GET以外のリクエストにはセキュリティトークンを使う
リクエストがユーザーの意図にあるものかを確認するために、同じWebアプリから生じたリクエストであることを証明するためのセキュリティトークンを発行、照合する
config.action_controller.default_protect_from_forgery
をtrueに設定すると自動的に実行(Rails5.2からデフォルトの設定)
form_with
などを使ったリクエストは自動でセキュリティトークン(authenticity_token
)が埋め込まれる
Ajaxリクエストへのセキュリティトークンの埋め込み
Ajaxリクエストでセキュリティトークンを送るには、JavaScriptが動く画面内にあらかじめセキュリティトークンを出力しておき、そのトークンを使ってX-CSRF-TokenというHTTPヘッダーで送る必要がある
出力はアプリのviewにある<%= csrf_meta_tags %>
fetch APIでAjaxリクエストを送信する場合は自分でトークンを埋め込む必要がある
fetch('/posts', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } })
※XSS脆弱性があると、あらゆるCSRF保護が迂回されるので注意
XSS(クロスサイトスクリプティング)
テキストフォームなどの出力処理に問題がある場合に、スクリプトなどを埋め込まれてしまう
フィッシング詐欺(別サイトへ誘導)、セッションハイジャック(cookieを取得しなりすましができる)
対策
- RailsはXSS対策が標準で組み込まれていて、view処理を実行した際にHTMLタグに対してエスケープ処理が行われる。
&
,"
,<
,>
は、&
,"
,<
,>
にそれぞれ置き換えられる - HTMLのエスケープ回避したいときのメソッド(html_safe/raw(
<%==
))を使う場合は、脆弱性が発生することになるので注意。html_escapeを使ってエスケープ。 - ある程度タグをそのまま出したいが、危険なタグを出力したくない場合はSanitize Helperでホワイトリスト方式で制限。
SQLインジェクション
パラメーター操作でDBをさわれてしまう
RailではSQL文字をフィルタする仕組みがあり'
, "
, NULL, 改行をエスケープされるので、滅多に起こらなくなった。
Model.find(id)
やModel.find_by()
といったクエリでは自動的にこの対策が適用されるが、
where("...")
、connection.execute()
、Model.find_by_sql()
については手動でエスケープする必要あり
対策
- 文字列をサニタイズ(無害化)する安全な書き方を使う
# クエリメソッドに対してハッシュで条件を指定 users = User.where(name: params[:name]) # プレースホルダを利用 users = User.where('name = ?', params[:name])
LIKEは演算子は自動的にサニタイズされないので注意。sanitize_sql_likeを使う必要がある
参考
- Rails セキュリティガイド - Railsガイド
- 安全なウェブサイトの作り方:IPA 独立行政法人 情報処理推進機構
- [ActiveRecord] SQLインジェクションを防ぐ|Railsの練習帳
学んだことがあれば都度追加していく。
Spotify APIを利用してプレイリストを作った
プログラミングを使って遊んでみた記事です。
最近札幌から東京に越してきたものの、札幌のラジオが恋しい…
NORTH WAVEというFM局をよく聴いていて、選曲がトレンドを押さえている&マイナーなアーティストもカバーしてくれているので、音楽をキャッチするのにいつもお世話になっていた。
同じJFL系のJ-WAVEもあるんだけど、似て非なる感じで。
radikoのプレミアム会員になれば解決なんだけど、そこまでの熱はないな…
そこで考えたのが、週に1回、北海道でよく聞かれた曲を集計し、ランキング化しているSAPPORO HOT 100の曲を詰め込んだプレイリストをつくること
このプレイリストを聴いておけばトレンドに触れられそう。
あとSpotifyは元々好きなサービスで、APIを利用してみたかったというのもある。
早速プログラムしてみる。
当初のイメージ
https://www.fmnorth.co.jp/hot100/からアーティストと曲を取得し保存 → Spotify APIでプレイリストを作成し取得した曲を追加 → Github Actionsで毎週定期実行
こんなプログラムを
require 'nokogiri' require 'open-uri' require 'dotenv/load' require 'rspotify' # サイトから曲とアーティストを取得 url = 'https://www.fmnorth.co.jp/hot100/' doc = Nokogiri::HTML(URI.open(url)) playlists = doc.css('.hot100List__detail').map do |row| track = row.at_css('.musicTitle').text.strip artist = row.at('.hot100List__detail--artist').text.strip { title: track, artist: artist } end # 承認リクエスト client_id = ENV['CLIENT_ID'] client_secret = ENV['CLIENT_SECRET'] RSpotify.authenticate(client_id, client_secret) # Spotifyのプレイリストを探し、先週分の全曲を削除 spotify_playlist = RSpotify::Playlist.find_by_id(ENV['PLAYLIST_ID']) track_numbers = (0..spotify_playlist.total - 1).to_a spotify_playlist.remove_tracks!(track_numbers, snapshot_id: spotify_playlist.snapshot_id) # Spotifyのプレイリストに今週分の曲を追加 tracks = playlists.flat_map do |list| RSpotify::Track.search("#{list.artist} #{list.track}", limit: 1, market: 'JP') end spotify_playlist.add_tracks!(tracks)
Spotify APIはRubyではrspotifyというRubyのラッパーがあり便利。
クライアントIDとクライアントシークレットを使って認証、プレイリスト内の曲をカスタマイズするといった流れ。
これを定期実行すればプレイリストができる!と思ったところでプレイリスト内の曲を削除や追加ができずに以下のエラー
uninitialized class variable @@users_credentials in RSpotify::User (NameError)
Spotify APIの認証について調べてみると、認証フローは4つ。
Authorization | Spotify for Developers
自分のはClient credentialsフローで、これだけだとユーザー認証が行われずプレイリストを触れないとのこと
ということでAuthorization Codeフロー(認可コードフロー)で再実装。
どうしても「サービスと連携しますか」みたいな画面で「はい」とする認証ダイアログが必要。
何やら自動化は難しそうだなと思い、慣れているRails×Omniauth×rspotifyという形で実装する。
viewに簡単なボタンを1つつけて、それを押すことで、認証→音楽を取得しDB保存→プレイリストに曲を追加とする形にした。
自動化はできなかったけど、ローカルでサーバー立ち上げて、ボタン1つでプレイリストが作れるんだから週に1回押すか….
と思ったけど、よくよく認可コードフローのページを読み直したり、以下のサイトを参考にすると、1度認証してしまえばrefreshed access_token
を使って自動化できそう。
今回は時間切れということで、いつかまた続きをやってみたい。
Spotify APIを触るのは楽しかった。
認証関連の知識がまだ曖昧だと@yocajiiさんとのペアプロ時に話したら、この本を紹介してもらったので、勉強してから再実装としたい。
そして以前に、@jnchitoさんの下の記事を読んだ時から、日常の困りごと何か解決したいなと思ってたので、その第一歩になったかな?
作ったプレイリストはこちら
いつでも札幌のトレンドが聞けるようになった。
結局radikoのプレミアム会員になるほうが早かったかも。
mysql2インストール時のエラー対応
AWSについて学習中。Railsアプリをあげようと思ったらエラーが出てややハマったのと、同様のエラー記事がなかったので残しておく。
Apple M1 ruby 3.1.3 Rails 7.0.4.1
gemからmysql2をインストールしようとすると以下のようなエラーが出た。
$gem install mysql2 Building native extensions. This could take a while... ERROR: Error installing mysql2: ERROR: Failed to build gem native extension. current directory: /Users/username/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/mysql2-0.5.5/ext/mysql2 /Users/username/.rbenv/versions/3.1.3/bin/ruby -I /Users/username/.rbenv/versions/3.1.3/lib/ruby/3.1.0 extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_gc_mark_movable()... yes checking for rb_wait_for_single_fd()... yes checking for rb_enc_interned_str() in ruby.h... yes ----- Cannot find library dir(s) /usr/local/opt/openssl@1.1/lib ----- *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir --without-opt-dir --with-opt-include --without-opt-include=${opt-dir}/include --with-opt-lib --without-opt-lib=${opt-dir}/lib --with-make-prog --without-make-prog --srcdir=. --curdir --ruby=/Users/username/.rbenv/versions/3.1.3/bin/$(RUBY_BASE_NAME) --with-openssl-dir --with-openssl-dir --with-openssl-include --without-openssl-include=${openssl-dir}/include --with-openssl-lib --without-openssl-lib=${openssl-dir}/lib To see why this extension failed to compile, please check the mkmf.log which can be found here: /Users/username/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/extensions/arm64-darwin-22/3.1.0/mysql2-0.5.5/mkmf.log extconf failed, exit code 1 Gem files will remain installed in /Users/username/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/mysql2-0.5.5 for inspection. Results logged to /Users/username/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/extensions/arm64-darwin-22/3.1.0/mysql2-0.5.5/gem_make.out
openssl関連で、--with-ldflags
や--with-cppflags
オプションをつければインストールできるという記事が多いのでやってみるが同じエラーが続く。
エラー文読み直しにて、Cannot find library dir(s) /usr/local/opt/openssl@1.1/lib
が怪しい。
そもそも/usr/local/opt/openssl@1.1/lib
というディレクトリがなく、そりゃ見つからないと怒られてしまう。
なんでこんなことになっているのかと思ったら.zshrc
に
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/opt/openssl@1.1"
という環境変数が設定されていた。多分、学習初期に訳もわからず設定したものな気がする。
これを存在するパスに。Home · rbenv/ruby-build WikiをみるとRuby 3.1以降ではOpenSSL3が必要とのことで
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@3)"
こうすることで無事にmysql2をインストールできた。
AtCoderで使ってるreturnって何?
AtCoderにてループを抜けるのにreturn
をよく使っている。
配列をループさせ該当するものがあれば何か出力して終了、該当しない場合は別の文字を返すというような問題。
sum = 0 numbers.each do |n| sum += n return puts '10を超えたよ' if sum > 10 end puts '10を超えなかったよ'
配列numbers
の要素を次々にsum
に加えていって、sum
が10超えたところで「10を超えたよ」、最終的に超えなければ「10を超えなかったよ」と出力される。
先日@haruguchiさん主催のモブプロ会にて、いつも通りreturn
を使ったんだが、このreturn
ってどうなってるんだっけと言語化できなかったのでメモ。
結論:トップレベルでのreturn
なので、出力後にそのままプログラムが終了した
array.each do |a| # トップレベルでの return なのでループを抜けるのではなくプログラムを抜ける return puts 'foo' if condition end
上のコードの様々な箇所でp self
とすると、全てmain
が返る。main
というのはトップレベルでselfを表すもの。
今回は何もメソッドやクラス、モジュールを作成していないので、each
の中でだろうが外だろうが、そこはトップレベルにある。
return
はメソッドからの脱出という認識でいたが、
トップレベルで return した場合はプログラムが終了します。
制御構造 (Ruby 3.2 リファレンスマニュアル)にそのまま書いてあった。
(といっても実務でのreturn
は早期リターン(ガード節)で使い、このような使われ方はなさそうな気が。)
プログラム全体を終了させるKernel.#exit (Ruby 2.7.0 リファレンスマニュアル)と同じことができる。
sum = 0 numbers.each do |n| sum += n if sum > 10 puts '10を超えたよ' exit end end puts '10を超えなかったよ'
ちなみにp self.class
でmain
のクラスを見るとObject。Objectクラスのインスタンスがmain
。
ObjectにはKernelモジュールがincludeされているので、トップレベルでp
やputs
が使える。
チェリー本にあった内容、なんだか点と点が繋がってきた感覚。
参考
AtCoderのB問題が解けるようになった
この記事はフィヨルドブートキャンプ Part 1 Advent Calendar 2022 - Adventarの13日目の記事です。
昨日はHikaruさんの「オブジェクト指向設計実践ガイド(POODR) 感想 & 第1章 まとめ - Light! More Light!」でした。
フィヨルドブートキャンプの今年のアドベントカレンダーはこちらです。
- フィヨルドブートキャンプ Part 1 Advent Calendar 2022 - Adventar
- フィヨルドブートキャンプ Part 2 Advent Calendar 2022 - Adventar
はじめに
こんにちは。フィヨルドブートキャンプ(以下、FBC)卒業生のガラムマサラです。
1日の学習初めの小手試しや、少しだけ時間があって何かコードを書きたいというときに、よくAtCoderの過去問を解いています。
これまでのAtCoderの取り組みや、B問題を解くためのTipsを本記事では紹介してみます。
AtCoderについて
AtCoderはプログラミングを使って問題を解くことを競技化したコンテストを開催するサイト・運営会社です。
AtCoder Beginner Contest、通称ABCは定期的に開催している初心者向けコンテストで、毎週A〜H問題が出題されています。
難易度としてA問題は「プログラミング言語の文法を確認する程度」、B問題は「FizzBuzzを応用する程度」とされています。
過去問が掲載されているので、コンテスト期間とは関係なく問題を解くことができ、各問題に解説があったり、他人の解答を見ることができるので、大変勉強になります。
AtCoderを使った勉強法に関しては、同じAtCoder学習仲間であるsugieさんが一昨日書かれた記事が参考になります。
わたしとAtCoder
FBCでRubyのプラクティスが終わった頃、同期のあんすとさんの紹介でAtCoderの過去問を解き始めました。
最初は標準入力ってどうやって受け取るの?という状態でしたが、それに慣れると少しずつA問題が解くことができました。
B問題となるとループを含んだ実装になるので、each
を使うのでやっとだった自分はほとんど手がつけられない状況でした。
FBCで学び、チーム開発や自作サービスでRubyのコードを書いていった中、久々にB問題に取り組んでみると、「あれ、なんだか解けるぞ!」とブレイクスルーを感じられて嬉しかったです。
多くのRubyistが言う「Rubyで書くが楽しい」ということも、なんとなく感じ取れている気がします。
本日までにB問題は81問解いていました。新しめの問題の方が難しいので、できるだけそちらを解くようにしています。
以降はそのB問題攻略の解き方のコツや便利なRubyメソッドを紹介、一緒に実際の問題も載せておきます。
AtCoder初心者の方の参考になるかもしれません(今回は解くことが目的で、計算量については考えられていないのであしからず…)
標準入力から受け取る
整数nを1つ受け取とった後、n行のスペース区切りの整数を二次元配列で受け取る方法です。
2 1 3 5 2 4 6
n = gets.to_i #=> 2 array = n.times.map { gets.split.map(&:to_i) } #=> [[1, 3, 5], [2, 4, 6]]
array = n.times.map do gets.split(' ').map do |line| line.to_i end end #=> [[1, 3, 5], [2, 4, 6]]
頻繁に出題される形です。
Array.new
やreadlines
を使うなど様々な方法がありますが、1つ得意な形を持っておくと応用も効きやすいので良いかなと思います。
全探索
考えられる全ての場合を調べることです。
表や二次元盤面を要する問題など、よくこの方法を使います。
主に二重、場合によって三重でeach
やtimes
を使って繰り返すことで全探索できることが多いです。
array = [1, 2, 3, 4, 5, 6, 7, 8, 9] count = 0 array.each do |i| array.each do |j| count += 1 if (i * j).even? end end puts count #=> 56
九九の表を1つ1つ評価しているイメージです。
全探索で解いた問題
全探索をするよりArrayやEnumerableモジュールのメソッドで簡単に解決できるものもあります。
Enumerable#each_with_index
要素の添え字付きの繰り返し処理です。
array = ['a', 'b', 'c'] array.each_with_index do |a, i| p "#{i}: #{a}" end #=> "0: a" # "1: b" # "2: c"
添え字を0以外からにしたいときにはEnumerator#with_indexを使います。
array.each.with_index(1) do |a, i| p "#{i}: #{a}" end #=> "1: a" # "2: b" # "3: c"
each_with_index
で解いた問題
Array#combinationとArray#permutation
combination
は並び順を考慮しない組み合わせを、permutation
は並び順を考慮する順列を生成するメソッド。
引数で選び出す要素の個数を指定できます。
array = [1, 2, 3, 4] array.combination(2).to_a #=> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] array.permutation(2).to_a #=> [[1,2],[1,3],[1,4],[2,1],[2,3],[2,4],[3,1],[3,2],[3,4],[4,1],[4,2],[4,3]]
重複を許可するArray#repeated_combinationとArray#repeated_permutationもあるので、このあたりは公式リファレンスを確認します(まだ使ったことがない気がする)
combination
で解いた問題
Enumerable#each_cons
要素を重複ありで引数nずつに区切り、ブロックに渡して繰り返すメソッド。
1回のループで要素を1つずつしか取得できないeach
に比べ、こちらは複数個の要素を扱えるので便利です。
array = [1, 2, 3, 4] array.each_cons(2){ |v| p v } #=> [1, 2] # [2, 3] # [3, 4]
each_cons
で解いた問題
Array#transpose
行と列の入れ換えをするメソッド。
個人的にFBCのプラクティス「ls コマンドを作る」で使ったメソッドなので思い出に残っています。
[[1, 2],[3, 4],[5, 6]].transpose #=> [[1, 3, 5], [2, 4, 6]]
transpose
で解いた問題
Array#find_index
index
としても同様です。配列内の指定した要素の最初の位置を取得するメソッド。
配列内に重複している要素があり、その一つだけを取り出したい場合に、「最初の」インデックス値を取得し操作できるので便利です。
Array#rindexは最後から順に探してくれます。
# 2をひとつだけ削除したい array = [1, 2, 2, 3, 3, 3] # これでは2を全て削除してしまう array.delete(2) array #=> [1, 3, 3, 3] # インデックス値を取得できるsと1つだけ削除可 array.delete_at(array.index(2)) array #=> [1, 2, 3, 3, 3]
index
で解いた問題
Enumerable#tally
要素を数え上げ、Hashで返すメソッド。
このメソッドは、僕のAtCoderの先生でもあるharuguchiさんから教えてもらいました。
tally
を使って簡潔に解ける問題も多く、個人的に一番好きなメソッドになりました。
['a', 'b', 'b', 'c', 'c', 'c'].tally #=> {"a"=>1, "b"=>2, "c"=>3}
tally
で解いた問題
配列の合計
配列の和はArray#sum、要素を全て掛けるにはEnumerable#injectを使っています
array = [1, 2, 3, 4] array.sum #=> 10 array.inject(:*) #=> 24
配列同士の積を求める問題
四捨五入・切り上げ・切り捨て
A問題から数字の四捨五入や切り上げ・切り捨てが出てくる頻度は高いです。
切り上げ切り捨てがどっちかよく混乱していたんですが、これもharuguchiさんから教えてもらったシーリングライト(天井)とフロア(床)のイメージで定着しました。
- 四捨五入:Numeric#round
- 切り上げ:Float#ceil
- 切り捨て:Float#floor
1.4.round #=> 1 1.5.round #=> 2 1.1.ceil #=> 2 1.9.floor #=> 1
四捨五入・切り上げ・切り捨てに関する問題
参考文献
プロを目指す人のためのRuby入門[改訂2版
FBCメンターの@jnchitoさんが執筆された、言わずと知れたチェリー本です。
配列、Range、Hash、繰り返し処理で迷ったときは必ずチェリー本に戻りました。答えはここに全部詰まっています!
プログラミングコンテストAtCoder入門
こちらは本記事を書くにあたって読んでみました。
Rubyではなく、C++とPython3のコードを用いて解説されているので、ソースコードに沿っての学習はできませんが、アルゴリズムを知る上で多くの知見が得られます。
この本によると自分は初級編をクリアしたところでした。中級編に進んでいきます!
さいごに
AtCoder運営の方々、いつも楽しませていただいています。ありがとうございます。 今後は実際に時間を調整してコンテストに参加してみたり、アルゴリズムの知識や計算量の意識が必要になってくるC問題以降に手をつけていきたいと考えています。
FBCでは、だいたい木曜日の昼頃にharuguchiさん主催でAtCoderを解く「勝手にモブプロ会」が行われています。
いつも楽しく学びのある会です。haruguchiさん、ありがとうございます!
FBCでAtCoderが気になっている方がいましたら、一緒にモブプロできると嬉しいです。
明日のFBCアドベントカレンダーは、Part1をkazumiさん、Part2をumizaruさんが記事を書いてくださいます。楽しみです!
Rails以外のgemをバージョンアップして、Dependabotを入れた
現在、フィヨルドブートキャンブ内にて、@Sakiさん主催の「パーフェクト Ruby on Rails輪読会」が行われています。 こちらの本、大変参考になりますが、なんだか難しいし、もう少し深掘りがしたい!と思いまして、このブログに予習・復習した内容を簡単に載せていきたいなと考えています。
9-2-1
ライブラリは新機能追加、バグ修正など日々更新されていくので、アプリで使っているgemの定期的なアップデートが必要。
Dependabotを導入しておくことで、bundle update
されたgemのPRが自動生成される。
ということで、自作アプリのNursePicksに導入してみる。
パRailsでは、導入前に手動でbundle update
することを推奨しているので、フィヨルドブートキャンプのメンターでもある伊藤さんのありがたい記事を参考に、ひとまずgemのバージョンアップをしていく。
はじめに、テストが通るか否かと、Simplecovでカバレッジも確認。
94%とそこそこの水準で書き漏れがないことも確認。
それではgemのバージョンアップ。
bundle outdated
というコマンドで、現バージョンと最新バージョンの差分を確認できる。
Gem Current Latest Requested Groups actioncable 6.1.6 7.0.4 actionmailbox 6.1.6 7.0.4 actionmailer 6.1.6 7.0.4 actionpack 6.1.6 7.0.4 actiontext 6.1.6 7.0.4 actionview 6.1.6 7.0.4 activejob 6.1.6 7.0.4 activemodel 6.1.6 7.0.4 activerecord 6.1.6 7.0.4 activestorage 6.1.6 7.0.4 activesupport 6.1.6 7.0.4 addressable 2.8.0 2.8.1 bootsnap 1.11.1 1.15.0 >= 1.4.4 default brakeman 5.3.1 5.4.0 >= 0 development, test buftok 0.2.0 0.3.0 bullet 7.0.2 7.0.4 >= 0 development capybara 3.37.1 d08e88d 3.38.0 5c86747 >= 0 test dotenv 2.7.6 2.8.1 dotenv-rails 2.7.6 2.8.1 >= 0 default erubi 1.10.0 1.11.0 faraday 1.10.0 2.7.1 faraday-http-cache 2.3.0 2.4.1 faraday-net_http 1.0.1 3.0.2 faraday-retry 1.0.3 2.0.0 http 4.4.1 5.1.0 http_parser.rb 0.6.0 0.8.0 i18n 1.10.0 1.12.0 jwt 2.3.0 2.5.0 meta-tags 2.16.0 2.18.0 >= 0 default metainspector 5.12.1 5.13.1 >= 0 default minitest 5.15.0 5.16.3 msgpack 1.5.1 1.6.0 multipart-post 2.1.1 2.2.3 oauth 0.5.10 1.1.0 oauth2 1.4.9 2.0.9 omniauth-google-oauth2 1.0.1 1.1.1 >= 0 default omniauth-oauth2 1.7.2 1.8.0 parser 3.1.2.0 3.1.3.0 pg 1.3.5 1.4.5 ~> 1.1 default public_suffix 4.0.7 5.0.0 puma 5.6.4 6.0.0 ~> 5.0 default rack 2.2.3 3.0.1 rack-mini-profiler 2.3.4 3.0.0 ~> 2.0 development rack-protection 2.2.0 3.0.4 rack-proxy 0.7.2 0.7.4 rack-test 1.1.0 2.0.2 rails 6.1.6 7.0.4 ~> 6.1.6 default railties 6.1.6 7.0.4 rb-fsevent 0.11.1 0.11.2 regexp_parser 2.4.0 2.6.1 rspec-core 3.11.0 3.12.0 rspec-expectations 3.11.0 3.12.0 rspec-mocks 3.11.1 3.12.0 rspec-rails 5.1.2 6.0.1 >= 0 development, test rspec-support 3.11.0 3.12.0 rubocop 1.29.1 1.39.0 rubocop-ast 1.18.0 1.23.0 rubocop-fjord 0.2.0 0.2.2 >= 0 development rubocop-performance 1.14.0 1.15.1 rubocop-rails 2.14.2 2.17.3 >= 0 development rubocop-rspec 2.11.1 2.15.0 >= 0 development selenium-webdriver 4.5.0 4.6.1 spring 4.0.0 4.1.0 >= 0 development sprockets 4.0.3 4.1.1 temple 0.8.2 0.9.1 tilt 2.0.10 2.0.11 tzinfo 2.0.4 2.0.5 unicode-display_width 2.1.0 2.3.0 zeitwerk 2.5.4 2.6.6
RailsはVue.jsの導入関連で6.1.6でnewしていたので、Rails関連を除くと大体がマイナーバージョンやリビジョンの更新になるっぽい。
半年前に作り始めた小規模アプリなので、bundle update
で一括バージョンアップしても良さそうだが、ここは基本に忠実に。
伊藤さんの記事には原則gemのバージョン指定はしないとあり。指定する場合はコメントを書いておくと良いとのこと。
不必要なバージョン指定を外す。
続いては、developmentとtestグループのgemを先にアップデート。
記事にある通りbundle update -g development -g test
とコマンドでアップデートしてみたが、自分の環境ではtestグループのみアップデートになってしまったので、(development + testグループがあったことが起因してそうだけど、深掘りはしない)
bundle update -g development bundle update -g test
としてアップデート。テストも問題なく通るし、ローカルでの挙動も問題なし。
再びbundle outdated
をしてみる。
Gem Current Latest Requested Groups actioncable 6.1.6 7.0.4 actionmailbox 6.1.6 7.0.4 actionmailer 6.1.6 7.0.4 actionpack 6.1.6 7.0.4 actiontext 6.1.6 7.0.4 actionview 6.1.6 7.0.4 activejob 6.1.6 7.0.4 activemodel 6.1.6 7.0.4 activerecord 6.1.6 7.0.4 activestorage 6.1.6 7.0.4 activesupport 6.1.6 7.0.4 bootsnap 1.11.1 1.15.0 >= 1.4.4 default buftok 0.2.0 0.3.0 dotenv 2.7.6 2.8.1 dotenv-rails 2.7.6 2.8.1 >= 0 default faraday 1.10.0 2.7.1 faraday-http-cache 2.3.0 2.4.1 faraday-net_http 1.0.1 3.0.2 faraday-retry 1.0.3 2.0.0 http 4.4.1 5.1.0 http_parser.rb 0.6.0 0.8.0 jwt 2.3.0 2.5.0 meta-tags 2.16.0 2.18.0 >= 0 default metainspector 5.12.1 5.13.1 >= 0 default msgpack 1.5.1 1.6.0 multipart-post 2.1.1 2.2.3 oauth 0.5.10 1.1.0 oauth2 1.4.9 2.0.9 omniauth-google-oauth2 1.0.1 1.1.1 >= 0 default omniauth-oauth2 1.7.2 1.8.0 pg 1.3.5 1.4.5 ~> 1.1 default puma 5.6.4 6.0.0 ~> 5.0 default rack 2.2.4 3.0.1 rack-protection 2.2.0 3.0.4 rack-proxy 0.7.2 0.7.4 rails 6.1.6 7.0.4 ~> 6.1.6 default railties 6.1.6 7.0.4 sprockets 4.0.3 4.1.1 temple 0.8.2 0.9.1
気になるのはoauth2が1.4.9→2.0.9くらい。
一応CHANGELOGを確認。Rubyの対応バージョンを変えたとかでした。
ということでbundle update
を実行
テストも通り、bundle outdated
をしても、残りはRails関連のgemだけでスッキリ。
Gem Current Latest Requested Groups actioncable 6.1.7 7.0.4 actionmailbox 6.1.7 7.0.4 actionmailer 6.1.7 7.0.4 actionpack 6.1.7 7.0.4 actiontext 6.1.7 7.0.4 actionview 6.1.7 7.0.4 activejob 6.1.7 7.0.4 activemodel 6.1.7 7.0.4 activerecord 6.1.7 7.0.4 activestorage 6.1.7 7.0.4 activesupport 6.1.7 7.0.4 buftok 0.2.0 0.3.0 http 4.4.1 5.1.0 http_parser.rb 0.6.0 0.8.0 puma 5.6.5 6.0.0 ~> 5.0 default rack 2.2.4 3.0.1 rails 6.1.7 7.0.4 ~> 6.1.6 default railties 6.1.7 7.0.4 temple 0.8.2 0.9.1
ここからはDependabotを導入していく。
パRailsで学習した形でyamlファイルを作ろうと思ったが、オプションに変更があったりなどで、GitHubのDocsやGitHub の Dependabot version updates で依存ライブラリを継続的に更新するという参考記事から学ぶ。
後者の記事はGithub上の動作を写真付きで載せてくれているので分かりやすかった。
# .github/dependabot.yml version: 2 updates: - package-ecosystem: 'bundler' directory: '/' schedule: interval: "weekly" day: "monday" time: "06:00" timezone: "Asia/Tokyo"
これにてDependabotの導入が完了。毎週月曜日に通知が来ることになった。
導入後は早速Railsのバージョンを上げてみようとの提案が…
次はRails7に上げてWebpackerを剥がせるとよさそう。
Brakemanを自作アプリに導入してみた
現在、フィヨルドブートキャンブ内にて、@Sakiさん主催の「パーフェクト Ruby on Rails輪読会」が行われています。 こちらの本、大変参考になりますが、なんだか難しいし、もう少し深掘りがしたい!と思いまして、このブログに予習・復習した内容を簡単に載せていきたいなと考えています。
9-3-2
Brakemanはアプリの脆弱性を静的解析してくれる便利なgem
RailsConfでの発表の動画を見る限り、読み方は「ブレーキマン」ではなく「ブレークマン」っぽい。
使い所はCIで定期実行して、脆弱性につながる疑いのコードを事前に検知するとよいとのこと。
XSSやSQLインジェクションなどの脆弱性を確認してくれ、その確認項目は30項目くらいありそう。
パRailsで作ったアプリでは警告等が出なかったので、せっかくなので自作アプリのNursePicksに導入してみる。
group :development do gem 'brakeman' end
bundle exec brakeman
で実行してみると、レポートが作成された。
== Brakeman Report == Application Path: work/nursepicks Rails Version: 6.1.6 Brakeman Version: 5.3.1 Scan Date: 2022-11-16 17:06:57 +0900 Duration: 1.213567 seconds Checks Run: BasicAuth, BasicAuthTimingAttack, CSRFTokenForgeryCVE, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EOLRails, EOLRuby, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, PermitAttributes, QuoteTableName, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeConfigCve, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TemplateInjection, TranslateBug, UnsafeReflection, UnsafeReflectionMethods, ValidationRegex, VerbConfusion, WithoutProtection, XMLDoS, YAMLParsing == Overview == Controllers: 11 Models: 6 Templates: 17 Errors: 0 Security Warnings: 4 == Warning Types == Cross-Site Scripting: 4 Cross-Site Scripting: 4 == Warnings == Confidence: Weak Category: Cross-Site Scripting Check: SanitizeConfigCve Message: rails-html-sanitizer 1.4.2 is vulnerable to cross-site scripting when `select` and `style` tags are allowed (CVE- 2022-32209). Upgrade to 1.4.3 or newer File: Gemfile.lock Line: 273 Confidence: Weak Category: Cross-Site Scripting Check: LinkToHref Message: Potentially unsafe model attribute in `link_to` href Code: link_to(Post.find(params[:post_id]).url, :target => :_blank, :rel => "noopener", :class => "post-image") File: app/views/posts/show.html.slim Line: 5 Confidence: Weak Category: Cross-Site Scripting Check: LinkToHref Message: Potentially unsafe model attribute in `link_to` href Code: link_to(Post.find(params[:post_id]).title, Post.find(params[:post_id]).url, :target => :_blank, :rel => "noopener") File: app/views/posts/show.html.slim Line: 9 Confidence: Weak Category: Cross-Site Scripting Check: LinkToHref Message: Potentially unsafe model attribute in `link_to` href Code: link_to(User.find(params[:id]).url, User.find(params[:id]).url) File: app/views/users/show.html.slim Line: 11
何やらCross-Site Scripting
で4件の警告あり。
内訳としては3件がLinkToHref
、1件がSanitizeConfigCve
1つ1つの警告の詳細についてはbrakeman/docs/warning_types/
link_to
メソッドの指摘に関しては
Brakemanはlink_toメソッドを使って作成されたURLにユーザー入力が含まれていると警告を発します。Railsにはこの方法で作成されたURLを安全にする方法がないため(例:プロトコルをHTTP(S)に限定する、など)、安全なメソッドを無視したい場合は次のオプションを使ってください。 Brakeman: Options
伊藤さんが翻訳してくれている。
ということで今回はurl
メソッドで怒られてるので、--url-safe-methods url
とオプションをつけておく。
あとはrails-html-sanitizer
をアップグレード。
対象コードを修正すると、
== Overview == Controllers: 11 Models: 6 Templates: 17 Errors: 0 Security Warnings: 0 == Warning Types == No warnings found
ということで修正完了。
GithubActionsにも設定、CIでも実行できるようにした。
- name: Breakeman run: bundle exec brakeman --url-safe-methods url
ちなみにbrakeman -I
コマンドで無視する警告を設定できるとのこと
参考記事:Brakemanで無視する警告の設定の仕方 - Qiita
breakman -A
でデフォルトでは検証しない、すべてのチェックも実行してみたが、Unscoped Findなど偽陽性のものも多いので、この設定をしておけばよさそう。
その他参考記事
- gem BrakemanでRails製アプリケーションの脆弱性を検知する | GMOアドパートナーズ TECH BLOG byGMO
- Rails環境でセキュリティ向上のため、Brakeman gemを導入&脆弱性対策を実施しました - Zeals TECH BLOG
セキュリティ対策ができるし、特に自作アプリでは変更のたびに誰かにレビューしてもらうことはないので、こういった静的解析ツールは便利!