Rails でのパスワード認証のセキュリティ対策

3 min読了の目安(約2800字TECH技術記事

Rails アプリケーションにおけるパスワード認証のセキュリティ対策について調べてみました。

パスワード認証に対する攻撃のバリエーション

総当たり攻撃(ブルートフォースアタック)

パスワードに使用できる文字や数字の組み合わせをすべて試す方法です。試行するパターンは膨大ですが、GPU など高速演算装置が用いられることで解読のスピードも速くなりました。総当たり攻撃に対しては、主に以下のような対策があります。

  • パスワードに使用する文字種や桁を増やす
  • ログイン試行回数を制限し、試行回数内にログインに成功しない場合はアカウントロックする

辞書攻撃

よく使われるパスワードの「辞書リスト」を使って、使用頻度の高いパスワード候補から順にログインを試す方法です。総当たり攻撃と同じくアカウントロックが有効です。

ジョーアカウント攻撃

アカウント名と同じ文字列をパスワードとしてログインを試す攻撃方法です。アカウント名と同じパスワードは設定できないようにするのが確実です。

パスワードリスト攻撃

別のサイトで漏洩したアカウントとパスワードを用いて、ログイン試行する攻撃方法です。

  • パスワードの使い回しを避けるようユーザーに促す

  • 2 要素認証

  • 漏洩したパスワードを設定しないように促す

対策

ログイン施行回数を制限する

認証処理に Devise を利用している場合は、Lockable の機能を有効にすることで可能です。

has_secure_password を使っている場合は Lockable のような機能はありませんが、 rack-attack を使うことで、大量のリクエストをブロックしたり、アクセス回数を制限したりすることができます。rack-attack は DoS攻撃の対策としても使われる gem です。

rack-attack の導入方法

Gemfile
+ gem 'rack-attack'
$ bundle

$ touch config/initializers/rack_attack.rb
config/initializers/rack_attack.rb
# Throttle login attempts for a given email parameter to 6 reqs/minute
# Return the email as a discriminator on POST /login requests
# 【訳】
# 指定されたメールアドレスに対するログイン試行を 60秒あたり6リクエストに抑えます
# メールアドレスを POST /login リクエストの区別要因として返します
Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req|
  if req.path == '/login' && req.post?
    req.params['email']
  end
end

上記のように設定することで、POST /login のリクエストを 60 秒あたり 6 回に制限できます。試行回数上限をオーバーしたときは「Retry later」と表示され、一定期間ログイン処理を実行できなくなります。

漏洩したパスワードを設定しないように促す

Have I Been Pwned? は、指定したパスワードやメールアドレスが漏洩しているかどうかを確認することができるサービスです。

そして、philnash/pwned は、上記サービスで提供されている Pwned Passwords API を Ruby から使うための gem です。(現在 API のバージョンは V3 が最新ですが、pwned gem には V2 を使用と記載されています。ただ、Pwned Passwords API のドキュメントを見る限り V3 と V2 の URI は同じようなので問題なさそうです。)

pwned の導入方法

Gemfile
+ gem 'pwned'
$ bundle

以下のようにして指定したパスワードが侵害されているかや侵害された回数を確認することができます。

> password = Pwned::Password.new("password")
> password.pwned?
=> true
> password.pwned_count
=> 3861493

また、pwned gem では Model のバリデーション として実装することもできるようです。

class User < ApplicationRecord
  validates :password, not_pwned: true
  # or
  validates :password, not_pwned: { message: "has been pwned %{count} times" }
end

漏洩したパスワードを指定した場合に、バリデーションエラーにして設定できないようにするのか、または警告を表示してユーザーに選ばせるのかはそのアプリケーションの方針によりますが、いずれにしても gem を使うことで簡単に実装できそうです。

参考