はじめに
Username Enumeration(ユーザ名列挙)は、サービスの応答の違い(エラーメッセージ、HTTPステータス、応答サイズ・時間など)を手がかりに「どのユーザ名が存在するか」を推測・収集する手法です。小さな情報漏洩が攻撃の入口になり得るため、現場では迅速に検出・対処することが重要です。ここでは TryHackMe の演習で使われる ffuf の例を交えつつ、仕組み・リスク・防御を実務的にまとめます。
何が問題か
- 存在するユーザ名が分かると、辞書攻撃(ブルートフォース)やフィッシングの成功率が大きく上がる
- 「存在する/しない」で異なる文言やステータスを返す設計は、情報漏洩になりやすい
- 攻撃者が大量自動試行できる状況では、被害が広がりやすい
なぜ差分が出てしまうのか(技術的要因)
- 差分化されたエラーメッセージ:存在の有無で文言を変えると一発でバレる
- HTTP ステータスやリダイレクトの差:200 / 302 / 404 の違いで判別可能
- レスポンスサイズ・テンプレートの差:テンプレートが変われば本文長が変わる
- 処理時間の差(タイミングチャネル):内部処理差で応答遅延が出る場合もある
- 設計上の過ち:サインアップやリセットの処理で「存在確認とエラー表示」の分離ができていない
実務的な対策(すぐやれる順に)
-
エラーメッセージの統一(即効)
- サインアップ/ログイン/パスワードリセットの失敗時の文言を揃える(例:「リクエストを受け付けました。メールを確認してください。」)
-
レート制限と CAPTCHA
- IP・アカウント単位の制限、段階的に CAPTCHA を導入
-
処理の均一化
- 存在・非存在で処理時間やレスポンスサイズが大きく変わらないようにする(ダミー処理等で均一化)
-
多要素認証(MFA)
- 列挙されても次の壁として MFA を要求
-
ログ・検知
- 同一IPや同一範囲の大量照会を監視して自動アラート/一時ブロック
-
CI/コードレビューでの検出
- テンプレート内の“存在判定フレーズ”を検出する静的チェックを入れる
コード/運用の具体例(防御向けスニペット)
統一メッセージ(PHP)
if (!validateCredentials($username, $password)) {
// 存在有無を区別しない統一レスポンス
http_response_code(401);
echo "ログインに失敗しました。入力を確認してください。";
exit;
}
簡易レート制限(擬似コード)
$key = "login_attempt:{$ip}";
$cnt = redis_incr($key);
redis_expire($key, 60); // 1分でリセット
if ($cnt > 10) {
http_response_code(429); // Too Many Requests
exit;
}
TryHackMe の例(概念を学ぶ実習)
演習では、Web のサインアップフォームのエラーメッセージを手掛かりに既存ユーザを列挙します。例として示される ffuf
コマンド(※演習教材の一部)は次の通りです(教材に従って学習環境で実行する想定):
user@tryhackme$ ffuf -w /usr/share/wordlists/SecLists/Usernames/Names/names.txt \
-X POST \
-d "username=FUZZ&email=x&password=x&cpassword=x" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u http://MACHINE_IP/customers/signup \
-mr "username already exists"
この例のポイント(要点):
-
-w
:チェックするユーザ名リスト(ワードリスト)を指定 -
FUZZ
:ワードリストの各行が入るプレースホルダ -
-X POST
/-d
/-H
:サインアップフォームへの POST リクエストを模す -
-mr
:ページ内の特定テキスト(ここでは"username already exists"
)があれば「既存ユーザ」と判定
注意(重要):上記は教材に含まれる学習用の例です。実際の環境での調査は必ず明示的な許可を取得してください。許可のない列挙やブルートフォースは不正アクセスや違法行為になります。
演習で得られるアウトプット
教材では、得られた既存ユーザ名を valid_usernames.txt
にまとめ、次のタスク(例:ログイン試行の学習)に使います。演習内の例では次のような発見がありました:
-
si***
で始まるユーザ名:simon -
st***
で始まるユーザ名:steve -
ro****
で始まるユーザ名:robert
(これは教材のサンプル結果です)
チェックリスト
- サインアップ/リセット/ログインのエラーメッセージは存在判定を漏らしていないか?
- 重要エンドポイントにレート制限が設定されているか?
- レスポンス時間・サイズの差で情報漏洩していないか?
- ログで列挙っぽい試行(短時間で大量)が検出できるようになっているか?
- クリティカルアカウントは MFA を必須にできるか?
まとめ
「出す情報は最小限に、追加の壁は厚く」。
「エラーメッセージの統一」と「レート制限」を実装すれば、Username Enumeration の被害リスクを大幅に下げられます。