パスワードスプレーは、少数のパスワードを多数のアカウントへ試す攻撃です。 1つのアカウントに失敗を集中させないため、アカウント単位のロックだけでは見逃しやすくなります。
リバースブルートフォース攻撃とは
通常のブルートフォース攻撃とは、試行する向きが異なります。 リバースブルートフォース攻撃は、1つまたは少数のパスワードを固定し、多数のアカウントIDへ順に試す攻撃です。
実務では、同じ発想の攻撃をパスワードスプレー(password spraying)と呼ぶことが一般的です。 攻撃者は「password123」「会社名2026」のような使われやすい候補を広い範囲へ試し、アカウントロックや検知を避けるために試行間隔を空けることもあります。
パスワードを使った主な不正ログインは、試す材料と向きで区別できます。
| 攻撃 | アカウントID | パスワード候補 | 1アカウントあたりの典型的な失敗数 |
|---|---|---|---|
| ブルートフォース | 1つまたは少数 | 多数の推測値 | 多い |
| パスワードスプレー | 多数 | 少数の共通候補 | 少ない |
| クレデンシャルスタッフィング | 漏洩リストにある多数 | そのIDと対になった漏洩パスワード | 少ないことが多い |
パスワードスプレーとクレデンシャルスタッフィングは、どちらも多数のアカウントへ少数回ずつ試すため、正規利用者の入力ミスに紛れやすくなります。 ただし、前者はよく使われる値を推測し、後者は他サービスなどから漏洩したIDとパスワードの組を再利用する点が異なります。
リバースブルートフォース攻撃が起きるパターン
典型的な弱点は次のとおりです。
- アカウント単位の失敗回数だけを監視し、複数アカウントを横断する試行を見ていない
- よく使われるパスワード、組織名を含む値、流出済みの値を登録できる
- エラーメッセージや応答時間の違いから、実在するアカウントIDを絞り込める
- 全員に同じ初期パスワードを配布し、利用前に無効化される一回限りの登録手順へ置き換えていない
- 古い認証プロトコルやMFAの対象外になっている入口が残っている
- IPアドレス、ネットワーク、端末、時間帯などを組み合わせた異常検知がない
メールアドレスの命名規則やログインID自体を、秘密情報として守り切ることは困難です。 アカウントの列挙を難しくする対策は必要ですが、IDが知られても侵入されない認証設計を前提にします。
対策法
弱いパスワードを登録させず、複数アカウントを横断する試行を検知し、パスワードが当たった後にも認証要素を残します。
- よく使われるパスワード、組織固有の予測しやすい値、流出済みのパスワードを登録時と変更時に拒否する
- パスキーやセキュリティキーなど、フィッシング耐性のある認証を優先する
- パスワードを残す場合も多要素認証を必須にし、SMSや単純なプッシュ承認だけに依存しない
- IPアドレスだけでなく、ネットワーク、端末、複数アカウントにまたがる失敗傾向を監視する
- エラーメッセージを統一し、実在するアカウントの有無による応答時間の差を小さくする
- 共通の初期パスワードを廃止し、短時間で失効する一回限りの登録リンクなどを使う
- 使用していない旧式の認証プロトコルや例外経路を無効にする
NIST SP 800-63Bは、パスワードの設定時と変更時に、よく使われる値、予測されやすい値、侵害済みの値を含むブロックリストと照合するよう求めています。 照合するのはパスワード全体であり、文字列に辞書語が部分的に含まれるだけで拒否する規則ではありません。
アカウントロックは、1つのアカウントへ試行が集中する攻撃には効果があります。 しかし、パスワードスプレーはしきい値に達しないよう少数回ずつ試せるため、ロックだけでは十分ではありません。 しきい値を過度に厳しくすると、攻撃者が多数のアカウントを意図的にロックするサービス妨害にも使えます。
Railsでの事例
登録時に弱いパスワードを拒否する処理は、認証ライブラリのバリデーションへ組み込みます。 次の例では、ブロックリストを起動時に読み込み、UnicodeをNFCへ正規化したパスワード全体と照合しています。
require "set"
class User < ApplicationRecord
validate :reject_blocklisted_password
PASSWORD_BLOCKLIST = Rails.root
.join("config/password_blocklist.txt")
.readlines(chomp: true, encoding: "UTF-8")
.map { |value| value.unicode_normalize(:nfc) }
.to_set
.freeze
private
def reject_blocklisted_password
return if password.blank?
normalized = password.unicode_normalize(:nfc)
if PASSWORD_BLOCKLIST.include?(normalized)
errors.add(:password, "は推測されやすいため使用できません")
end
end
end
この例の安全性は、ブロックリストの内容と更新方法に依存します。 公開された上位パスワードだけでなく、サービス名や組織名などの文脈固有の候補も含めます。 流出済みパスワードを外部サービスと照合する場合は、平文のパスワードを送信せず、k-Anonymityなどに対応した保守中のライブラリやAPIを利用します。
検知側では、アカウント単位だけでなく送信元単位の制限も重ねます。
# config/initializers/rack_attack.rb
class Rack::Attack
throttle("logins/ip", limit: 20, period: 5.minutes) do |req|
req.ip if req.path == "/login" && req.post?
end
end
しきい値は例であり、利用者数、NAT配下の利用状況、通常の失敗率を計測して調整します。
リバースプロキシを使う場合は、信頼するプロキシを正しく設定し、利用者が任意の転送ヘッダでreq.ipを偽装できないようにします。
送信元を分散する攻撃はIPアドレスだけでは止められません。 認証基盤やSIEMで複数アカウントへの失敗、通常と異なる端末や地域、試行後の不審な成功を相関させ、侵害が疑われる場合はセッション失効と認証情報の再設定まで行います。
