説明
パスワード検証や認証ミドルウェアだけでは、ログインの繰り返しを制限できません。試行回数の制限、遅延、ロック、CAPTCHAなどがなければ、攻撃者がパスワードの推測を繰り返せる可能性があります。認証の成否にかかわらず、試行回数を追跡して制限する必要があります。
想定される影響
- アカウントが総当たりやクレデンシャルスタッフィングの攻撃にさらされるおそれがあります。
- 失敗を追跡していないと、攻撃の検出や対応が遅れる可能性があります。
対処方法
- 要件に応じて、アカウント、IPアドレス、デバイスごとのログイン失敗を追跡してください。
express-rate-limitなどのミドルウェアで、ログイン先のエンドポイントに制限を適用してください。- ロックと解除のイベントをセキュリティログやアラートに記録してください。
例
本文の解析、rateLimit の初期化、ユーザーの取得とエラー処理を省略したルートの抜粋です。実際の構成では、プロキシ経由のクライアントIPの扱い、IPv6のアドレス集約、複数インスタンス間の共有カウンターも設定してください。
変更前
javascript
app.post("/login", async (req, res) => {
const user = await loadUser(req.body.username);
const ok = await bcrypt.compare(req.body.password, user.passwordHash);
res.json({ ok });
});
変更後
javascript
const loginLimiter = rateLimit({
windowMs: 60_000,
max: 5,
keyGenerator: (req) => `${req.ip}:${req.body.username ?? ""}`,
});
app.post("/login", loginLimiter, async (req, res) => {
const user = await loadUser(req.body.username);
const ok = await bcrypt.compare(req.body.password, user.passwordHash);
res.json({ ok });
});
解説:
- 変更前: 試行回数の制限がないため、パスワードの推測を繰り返し認証ハンドラーへ送れます。
- 変更後: 同じIPとユーザー名の組み合わせに対して、1分間に5回のリクエストを許可します。失敗だけを数えたり、アカウントのロックやデバイス別制限を実装したりする例ではありません。値を変える試行にも対応するため、アカウントまたはIP単独の制限も併用してください。