「このメールアドレスは登録されていません」
サービスのログインフォームに表示されるこの一文が、攻撃者にとって貴重な情報源になっていることをご存知でしょうか。
アカウント列挙(Account Enumeration)は、Webアプリケーションの応答の差を突いてユーザー名やメールアドレスの存在を確認する脆弱性です。直接的なパスワード窃取ではありませんが、フィッシングやパスワードスプレー攻撃の「事前調査」として悪用されています。
この記事では、アカウント列挙の仕組み・攻撃者がどのフォームを狙うか・情シスがシステム設計段階から取り入れるべき対策を、現場で使えるレベルで解説します。

アカウント列挙とは?
アカウント列挙とは、Webサービスのログインフォーム・新規登録フォーム・パスワードリセットフォームなどが返す応答の差(エラーメッセージ・応答時間・HTTPステータスコードなど)を利用して、特定のユーザー名やメールアドレスがシステムに登録されているかどうかを特定する攻撃手法です。
認証情報の一部(IDやメールアドレス)が「存在する」とわかるだけで、攻撃者は次の一手を打てます。
・パスワードスプレー攻撃: 存在が確認されたアカウントに対して、よく使われるパスワードを試す
・標的型フィッシング: 実在するユーザー名を使った巧妙なメールを送る
・パスワードリスト攻撃: 過去の漏洩データベースと照合して認証情報を試す
・アカウントテイクオーバー: 上記の攻撃を組み合わせてアカウントを乗っ取る
アカウント列挙そのものはシステムへの直接侵入ではありませんが、より深刻な攻撃への足がかりとして機能します。OWASPでも、認証の失敗(OWASP A07)に関連するリスクとして取り上げられています。
攻撃の仕組み(敵を知る)
攻撃者はどのようにしてアカウントの存在を確認するのでしょうか。主な手口を防御目的で解説します。
1. エラーメッセージの差異を悪用する
最も単純で多く見られる手口です。
想定例として、次のような応答パターンのサービスを考えてみます。
・存在しないメールアドレスを入力 →「このメールアドレスは登録されていません」
・存在するメールアドレス+誤パスワード →「パスワードが違います」
攻撃者にとって、これは「メールアドレスが存在するかどうか」を教えてくれる仕組みになっています。メールアドレスのリストに対してログイン試行を繰り返すだけで、有効なアカウント一覧を自動的に取得できます。
2. 応答時間(タイミング)の差を悪用する
エラーメッセージを統一しても、処理にかかる時間が情報漏洩の原因になることがあります。
想定例として、次のパターンを考えます。
・存在しないユーザー → DBを検索して即座に「認証失敗」を返す(例: 数ms)
・存在するユーザー → DBを検索 → パスワードハッシュ比較処理が走る(例: 数百ms)
この時間差を大量のリクエストで計測することで、アカウントの存在を推測できます。タイミングサイドチャネル攻撃(Timing Attack)と呼ばれる手法です。
3. パスワードリセットフォームの悪用
「パスワードを忘れた方はこちら」のフォームも盲点になりがちです。
・存在しないメールアドレス →「このメールアドレスは登録されていません」
・存在するメールアドレス →「パスワードリセット用のメールを送りました」
ログインフォームで対策済みでも、パスワードリセット機能で同じ情報漏洩が起きているケースは少なくありません。
4. 新規登録フォームの悪用
会員登録画面も同様のリスクがあります。
・既存ユーザーと同じメールアドレスを入力 →「このメールアドレスはすでに登録済みです」
・未登録のメールアドレス →「登録を完了しました」
このパターンでも攻撃者はアカウントの存在を確認できます。
5. HTTPレスポンスコードやリダイレクト先の差
エラーメッセージのテキストを統一しても、HTTPステータスコード(200/401/403/404など)やリダイレクト先のURLが認証成功・失敗で異なる場合、同様の情報漏洩が発生します。レスポンスボディの長さに差がある場合も注意が必要です。
具体的な防御手順
1. エラーメッセージを統一する
ログイン・パスワードリセット・新規登録の各フォームで、アカウントの存在有無が判別できるメッセージを返さないことが最も基本的な対策です。
# NG: アカウントの存在が特定できてしまう 「このメールアドレスは登録されていません」 「パスワードが間違っています」(メールが正しい場合のみ表示) # OK: 認証失敗の理由を明示しない 「メールアドレスまたはパスワードが正しくありません」 # パスワードリセットでも同様に統一 「入力いただいたメールアドレスが登録済みの場合、 パスワードリセット用のメールをお送りします。」
新規登録では「このメールアドレスはすでに登録済みです」の代わりに、「ご登録いただいたメールアドレス宛に確認メールをお送りします」と返す方式(既存ユーザーには「ログイン案内メール」を送付する)が有効です。
2. レート制限(Rate Limiting)を実装する
エラーメッセージを統一しても、大量の自動試行を防ぐためにレート制限は必須です。
・IPアドレス単位の制限: 同一IPからの認証試行が一定回数を超えたらブロックまたは遅延させる
・アカウント単位の制限: 特定アカウントへの試行回数が閾値を超えたら一時ロックまたは追加認証を要求する
・グローバル監視: 特定時間内の認証失敗数が急増したらアラートを上げる
Linuxサーバーレベルではfail2banによるIPブロックが有効ですが、アプリケーション層での対策も並行して実装することが重要です。
3. CAPTCHA・ボット対策を導入する
一定回数の認証失敗後にCAPTCHAを表示することで、自動化ツールによる大量試行を防ぎます。最初から全認証にCAPTCHAを要求するとユーザビリティが下がるため、「不審な挙動を検知した場合に追加する」アダプティブな実装が現実的です。
4. タイミング攻撃への対策(処理時間の均一化)
処理時間の差を攻撃者に利用されないよう、アカウントの存在有無にかかわらず、常に同等の処理時間を確保する実装が必要です。
# 疑似コード例(概念の説明) def verify_login(input_email, input_password): user = find_user_by_email(input_email) if user: # 実際のパスワードハッシュ比較 result = check_password(input_password, user.password_hash) else: # ユーザーが存在しない場合もダミーのハッシュ比較を実行 # → 処理時間を均一化してタイミング差を消す check_password(input_password, DUMMY_HASH) result = False return result
5. HTTPレスポンスの統一
・認証失敗は常に同じHTTPステータスコードを返す(一般的には401、またはエラー内容を隠す場合は200)
・認証成功・失敗でリダイレクト先のURLが異なる場合は統一する
・レスポンスボディの長さが認証成功・失敗で極端に異なる場合も注意する
中小企業でも今日からできること
自社開発のWebアプリがない場合でも、利用しているSaaSやCMSの設定確認から始められます。
・WordPressなどのCMS: デフォルトでは「ユーザー名が無効です」と「パスワードが正しくありません」が別々に表示されます。セキュリティプラグインを使って統一されたメッセージに変更しましょう
・ログイン試行の監視: 認証失敗ログを定期的に確認し、同一IPからの大量失敗がないかチェックします
・開発委託先への要件明示: 社内システムやWebサービスを外部委託している場合、エラーメッセージの統一とレート制限の実装を開発要件として契約書・仕様書に明記します
・定期的なフォームテスト: ログイン・パスワードリセット・新規登録の各フォームで、登録済みのアドレスと未登録のアドレスを入力したときのメッセージ・応答時間に差がないか定期的に確認します
パスワードスプレー攻撃やパスワードリスト攻撃は、アカウント列挙で特定した有効なID一覧を元に実行されることが多いため、列挙の入口を塞いでおくことが重要です。
Linuxサーバーのアクセス制御全般については、姉妹サイトLinuxMaster.JPでも詳しく解説しています。
よくある誤解と注意点
【注意1】「うちは有名じゃないから狙われない」は禁物
アカウント列挙は、標的を絞った攻撃だけでなく、無差別にWebアプリをスキャンするボットによっても実行されます。サービスの規模や知名度に関係なく、ログインフォームさえあれば試みられます。
【注意2】フロントエンドのバリデーションだけでは不十分
JavaScriptで「このメールアドレスは登録済みです」とリアルタイム表示する機能は利便性が高い反面、その判定がAPIエンドポイントを通じて行われる場合、そのAPIが列挙に悪用されます。サーバー側での対策が不可欠です。
【注意3】MFAがあれば安全とは限らない
多要素認証(MFA)はアカウント侵害への最終防御として有効ですが、アカウントの存在が漏洩すること自体は防げません。アカウントテイクオーバーを防ぐためにも、列挙対策は別途必要です。
【注意4】パスワードリセット機能を見落としない
ログインフォームで対策済みでも、パスワードリセット機能でアカウントの存在が漏洩するケースは一般的に報告されています。全フォームを一貫して対策することが重要です。

本記事のまとめ
| 攻撃手口 | 悪用される差異 | 対策 |
|---|---|---|
| エラーメッセージ差異 | 「未登録」vs「パスワード違い」の別々表示 | 「IDまたはパスワードが正しくありません」に統一 |
| タイミングサイドチャネル | 処理時間の差(ハッシュ比較の有無) | ダミーハッシュ比較で処理時間を均一化 |
| パスワードリセット悪用 | 「未登録」vs「メール送信済み」の別々応答 | どちらも「送信しました」で統一(既存ユーザーには別途案内) |
| 新規登録フォーム悪用 | 「登録済み」エラーの表示 | 登録済みでも「確認メールを送りました」で統一 |
| 自動化ツールによる大量試行 | フォームへの無制限アクセス | レート制限・CAPTCHA・fail2ban |
アカウント列挙は地味に見えて、パスワードスプレー・フィッシング・アカウントテイクオーバーへの入口になる重要な脆弱性です。「エラーメッセージの統一」というシンプルな対策から始め、レート制限・タイミング攻撃対策を順に実装していくことで、攻撃者が最初の一歩を踏み出しにくい環境を作れます。
PR
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版(徳丸浩/SBクリエイティブ)
アカウント列挙を含むWebアプリケーション脆弱性の仕組みと対策を、実際のコードとともに丁寧に解説した定番書。情シス・開発者どちらにもおすすめの一冊です。
