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

こんにちは。セキュリティの世界へようこそ。

現場でバリバリコードを書いているエンジニアの皆さん、あるいは「セキュリティって何から手をつければいいの?」と悩んでいるIT担当者の皆さん。今日は、セキュリティの「入り口」でありながら、意外と見落とされがちな「ログイン画面のエラーメッセージ」という小さな穴についてお話しします。

ここを少し工夫するだけで、あなたのサービスは格段に「泥棒が嫌がる家」になりますよ。

—

泥棒は「家の中」に入る前に、まず「表札」を確認する

想像してみてください。あなたは今、泥棒の視点に立っています。狙うのは、セキュリティがガバガバな家ではなく、入りやすそうな家です。

泥棒は、いきなり鍵を壊すようなリスクの高いことはしません。まずは、「この家にはどんな人が住んでいるのか?」という表札を確認します。

  • 「あ、この家は〇〇さん(会員)がいるな」
  • 「この家はパスワードを間違えても教えてくれる優しい家だな」

ログイン画面で、ユーザーがIDやパスワードを間違えた時に「ユーザーが存在しません」や「パスワードが違います」と律儀に教えてしまうシステム。これは、泥棒に対して「このIDは実在するから、あとはパスワードだけ総当たりで試せば入れるぞ」と親切に教えているようなものなんです。

これが、セキュリティ業界で言う「ユーザー列挙攻撃(User Enumeration)」の入り口です。

—

「優しさ」が「脆弱性」になる瞬間

開発者としては、ユーザーがログインに失敗した時に「IDが違うのか、パスワードが違うのか」を教えてあげたほうが親切ですよね。ユーザーも「あ、IDを打ち間違えたかな?」とすぐに気づけます。

しかし、セキュリティの現場では、「親切=攻撃者へのヒント」になり得ます。

攻撃者はツールを使って、何万というIDリストをあなたのログイン画面に流し込みます。その際、エラーメッセージの違いを監視するだけで、「実在するユーザーIDのリスト」を瞬時に作り上げてしまうのです。一度リストが完成すれば、あとはそのIDに対してパスワードの総当たり(ブルートフォース)を仕掛けるだけ。

だからこそ、私たちは「誰であろうと、同じ返答をする」というルールを徹底します。

—

「曖昧さ」こそが最強の防犯対策

対策はシンプルです。「IDとパスワードのどちらが間違っているか、決して判別させない」こと。

ログイン失敗時に出すメッセージは、以下のように統一しましょう。

❌ ダメな例:

  • 「ユーザー名が見つかりません」
  • 「パスワードが一致しません」

✅ 推奨される例:

  • 「メールアドレスまたはパスワードが正しくありません」

これなら、攻撃者は「IDが存在するのか、パスワードが違うのか」を判断できません。これが、泥棒に「この家は表札を隠しているから、入るのが面倒くさそうだ」と思わせる防犯対策なのです。

—

実装で気をつける「もう一つの罠:レスポンス時間」

実は、メッセージを統一しただけではまだ安心できません。プロの攻撃者は、「レスポンスが返ってくるまでの時間(ミリ秒単位の差)」まで見ています。

例えば、プログラムの処理順序がこうなっているとします。

1. DBを検索する(ユーザーがいるか確認)
2. ユーザーがいればパスワードを照合する

この場合、ユーザーが存在する場合としない場合で、処理にかかる時間に微妙な差が出ます。「ユーザーが存在する時の方が、パスワード照合の分だけ0.01秒遅いな」と気づかれてしまうのです。

実装のヒント(コード例)

これを防ぐには、ユーザーがいようがいまいが、一定の処理を通す工夫が必要です。

// 簡易的な擬似コードの例です
async function login(username, password) {
// 1. ユーザーを検索(ダミーユーザーを用意しておく考え方もあります)
const user = await findUserByUsername(username);

// 2. ユーザーが存在しなくても、存在する場合と同じ時間をかけるために
// 意図的に偽のパスワードハッシュと比較を行う(定数時間比較)
const hashToCompare = user ? user.passwordHash : dummyHash;

const isValid = await verifyPassword(password, hashToCompare);

// 3. メッセージは常に同じにする
if (user && isValid) {
return “ログイン成功”;
} else {
// どちらが間違っていても、同じメッセージを返す
return “IDまたはパスワードが正しくありません”;
}
}

※ 本番環境では、定数時間で比較するライブラリ(crypto.timingSafeEqualなど)を使うのが鉄則です。

—

最後に:セキュリティは「完璧」を目指さなくていい

セキュリティに完璧はありません。ですが、「攻撃のコスト」を上げることなら誰でもできます。

ログイン画面を少しだけ不親切にする。たったそれだけで、無差別な攻撃のターゲットから外れることができます。セキュリティとは、難しい呪文を唱えることではなく、「どうすれば相手が諦めてくれるか」を想像する知恵のことなんです。

まずは明日、あなたのサービスのログイン画面を確認してみてください。「優しすぎるエラーメッセージ」が出ていませんか?

一歩ずつ、泥棒が嫌がる強いシステムを作っていきましょう。また次回の記事でお会いしましょう!

コメント

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