【実務・中級編】認証失敗時の情報漏洩(ユーザー列挙攻撃)を防ぐエラーメッセージ設計 – アプリケーションセキュリティ & 安全な開発防御ガイド

ログイン画面は「攻撃者の入り口」だ。なぜ「ユーザーが存在しません」と言ってはいけないのか?

現場でコードレビューをしていると、未だに「親切すぎる」エラーメッセージに出くわすことがある。「ご入力いただいたメールアドレスは登録されていません」——これ、UXとしては100点満点かもしれないが、セキュリティの観点では「どうぞ、このリストを総当たりしてアカウントを特定してください」と招待状を送っているのと同じだ。

今日は、なぜこの「ユーザー列挙(User Enumeration)」が致命的なのか、そして現場で即採用できる「攻めの防衛術」について話そう。

—

1. なぜ「ユーザー列挙攻撃」が恐ろしいのか

攻撃者は、流出したデータベースのメールアドレスリストや、辞書ファイルを使ってログインエンドポイントを叩く。もしサーバーが「ユーザー有無」でレスポンスを変えたらどうなるか?

  • 存在するアカウント: 「パスワードが違います」
  • 存在しないアカウント: 「ユーザーが見つかりません」

このわずかな差異だけで、攻撃者は「実在する標的リスト」をノーコストで完成させる。このリストが完成すれば、次はブルートフォース(総当たり)や、他サイトからの流出パスワードを流用する「リスト型攻撃」へとステップアップする。最初の防壁が崩れると、システム全体がドミノ倒しになるのは時間の問題だ。

—

2. 実践:絶対にやってはいけない実装と、あるべき姿

まずは、PHPによる「やってはいけない」例と、それを修正した「セキュアな設計」を見てほしい。

悪い例:ヒントを与えすぎてしまう実装

// 脆弱な実装:存在確認で分岐してしまう
$user = $db->findUserByEmail($email);
if (!$user) {
return “メールアドレスが見つかりません”; // ここで列挙可能に
}
if (!password_verify($password, $user->hash)) {
return “パスワードが間違っています”;
}

良い例:応答時間を一定にするセキュアな実装

エラーメッセージを統一するだけでは不十分だ。実は「レスポンス速度の差(タイミング攻撃)」でもユーザーの存在はバレる。ダミーのハッシュ計算を挟むことで、処理時間を正規化するのがプロの仕事だ。

// セキュアな実装:常に同じメッセージ、かつ一定の処理時間を担保
$user = $db->findUserByEmail($email);
$genericError = “メールアドレスまたはパスワードが正しくありません”;

// ダミーのパスワードハッシュ計算(存在しない場合でも計算時間を稼ぐ)
$dummyHash = ‘$2y$10$abcdefghijklmnopqrstuv’;

if (!$user) {
// ユーザーがいなくても、あたかも存在するかのようにハッシュ検証を実行
password_verify($password, $dummyHash);
return $genericError;
}

if (!password_verify($password, $user->hash)) {
return $genericError;
}

// 認証成功の処理…

—

3. インフラレイヤーでの「追い打ち」防御

アプリ層での対策に加えて、WAF(Web Application Firewall)やレートリミットの設定は必須だ。どんなにコードを堅牢にしても、IP単位で1秒間に100回ログインを試行されたら、その負荷でサービスが落ちる。

Nginxでのレートリミット設定例

nginx.conf に以下の設定を追加し、特定のIPからのログイン試行を物理的に制限する。

ログインエンドポイントへのアクセスをIPごとに制限
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

location /api/login {
limit_req zone=login_limit burst=5 nodelay; # 1分間に5回まで。バーストは5回まで即時許可
proxy_pass http://backend;
}

—

4. 最後に:セキュリティは「バランス」ではなく「設計」

「そんなに厳しくしたらユーザーがログインできなくなる」という声が、開発チームから上がることもあるだろう。だが、考え方を変えてほしい。

  • 「ユーザーが存在しません」と言わないことは、ユーザーを突き放すことではない。
  • 「認証情報を確認してください」と伝えることは、最も誠実なガードレールだ。

もし本当にパスワードを忘れたユーザーを救いたいなら、ログイン画面ではなく「パスワードリセット機能」のフローを磨き込め。認証の入り口はあくまで「誰であろうと入り口は一つ」という厳格な姿勢を貫くべきだ。

明日、君のコードベースを見直してほしい。もし「ユーザーが見つかりません」という文字列があったら、それが君のシステムの最大の弱点だ。今すぐ修正しよう。それが、プロのエンジニアとしての最低限の矜持だ。

コメント

タイトルとURLをコピーしました