Railsでアプリケーションを構築する際、公開前に脆弱性検査用のツールや専門の第三者によって脆弱性がないか検査すると安全です。
デフォルト状態のRailsアプリケーションでは、残念ながらこの検査によりいくつかの脆弱性が発見されることになります。
診断結果への対応は大変骨が折れる作業になることが多く、また実施フェーズもスケジュールの後ろの方になりがちなため、可能な限り早い段階で対応しておきたいものです。
今回はこういった脆弱性のうち、セッションストレージに着目した対応の一つをご紹介します。
※このコラムのプログラム等は以下の環境で動作確認しています
(2023/06/30最終確認)
ruby: ruby 3.2.2 [arm64-darwin22]
rails: Rails 7.0.5.1
activerecord-session_store: 2.0.0
セッションストレージ
Railsではセッション変数を容易に扱える仕組みとして「セッションストレージ」が用意されており、デフォルトではCookieStore
(ActionDispatch::Session::CookieStore)
が利用されます。
これはセッション変数を含めたセッション情報をクライアントのCookieとして暗号化された状態で保存します。
「暗号化されているから安心!」と思いきや、実際は以下のような問題を抱えています。
問題: サーバー側でセッションを無効化できない
セッションを初期化、無効化する目的でreset_session
を実行したとします。
サーバー側では新しいセッション情報を発行し、既存のCookieを上書きするようなレスポンスを返します。
ところが、クライアント側で上書きされる前のCookieを確保しておくと、その時点のセッションを復元できてしまいます。
正しい形式で暗号化されたCookie情報をクライアントから受信した場合、サーバー側ではそのセッションが無効化されたものかどうか判別することができません。
この挙動は、例えばセッション変数にログイン情報を保存している場合に問題となります。
サーバー側で当該ユーザーの全セッションを無効化したつもりができていなかった、という状況が起こり得ます。
対策: セッションストレージを切り替える
この問題に対応するには、セッション情報をサーバー側で保持する必要があります。
サーバー側で保持する、ということは一般的にデーターベースを利用することになりますが、これを自前で実装してしまうと煩雑になってしまいます。
そこで利用したい機能が、セッションストレージとして指定できるGem,activerecord-session_store
です。
activerecord-session_store
このGemを導入し、セッションストレージとして:active_record_store
(ActionDispatch::Session::ActiveRecordStore)
を指定すると、これまでクライアントのCookieに保存されていたセッション変数がActiveRecordを通してデータベースに保存されるようになります。
導入方法
GitHubの記載を参考にすると、簡単に導入することができます。
Gemfile
に記述を追加し、bundle install
します。
Gemfile
gem 'activerecord-session_store' # 追記
コマンドを実行し、マイグレーションファイルを生成します。
bin/rails g activerecord:session_migration
コマンドを実行し、マイグレーションを行います。
bin/rails db:migrate
続いてsession_store
を指定します。
これはconfig/application.rb
やconfig/initializers/session_store.rb
などで指定できます。keyexpire_after
は適切に設定してください。
config/application.rb
module YourApp
class Application < Rails:: Application
# ~~~
config.session_store :active_record_store, key: '_your_app_session', expire_after: 14.days
end
end
または、
config/initializers/session_store.rb
Rails.application.config.session_store :active_record_store, key: '_your_app_session', expire_after: 14.days
以上でセッションストレージの切り替えは完了です。
以降、クライアントのCookieにはセッションIDのみが保存され、セッション情報そのものはサーバー側のデータベースに保存されるようになります。
サーバー側でセッション情報を無効化すると、仮にクライアント側から無効化される前のCookie情報が送られてきたとしても、不正な値として無視することができます。
これにより、セッション情報をサーバー側でコントロールできるようになります。
無効になったセッション情報を削除する
なお、このままですと有効期限を迎えたセッション情報がデータベースに残り続けてしまいます。
セッション情報が保存されているテーブル、sessions
をきれいにするためのrakeコマンドが用意されていますので、適宜利用します。
bin/rake db:sessions:clear
- セッション情報がすべて削除されます。開発環境下で便利です
- ただし、
TRUNCATE TABLE
を実行するためsqliteでは利用できません…
- ただし、
- セッション情報がすべて削除されます。開発環境下で便利です
bin/rake db:sessions:trim
- 一定期間更新されていないセッション情報が削除されます。
- デフォルトは
30
日で、SESSION_DAYS_TRIM_THRESHOLD
環境変数で指定することができますSESSION_DAYS_TRIM_THRESHOLD=0 bin/rake db:sessions:trim
とすることですべて削除できます。こちらはsqliteでも利用できます
- デフォルトは
- 一定期間更新されていないセッション情報が削除されます。
本番環境ではcrontab
などでスケジュール設定して実行することが適切でしょう。
Gem whenever
をおすすめします。
config/schedule.rb
# 毎日3時に古いセッション情報を削除する
every 1.day, at: '3:00' do
rake 'db:sessions:trim'
end
まとめ
Railsアプリケーションのセッションストレージの脆弱性対応として、activerecord-session_store
を利用する方法をご紹介しました。CookieStore
は扱いやすく便利な仕組みですが、セキュリティ観点では決して堅牢とはいえません。
アプリケーション開発の最初に、その他のセッションストアを設定してしまった方が安全です。
毎回の作業になりますので、私は前回ご紹介したboilerplateに含めています。
なお、これだけではまだセッション周りの脆弱性が残っていることがあります。
Rails セキュリティガイドを参照の上、脆弱性を作り込むことが無いようくれぐれも注意して開発に取り組みましょう。
お知らせ
当委員会ではRails試験を全国300か所で一年中実施をしています。興味がある方は以下をご覧の上、是非受験ください。https://railsce.com/exam