認証バイパスの現実:レート制限は「飾り」ではない
現場でインシデント対応をしていると、いまだに「ログイン画面にCAPTCHAを置いたから安心」とか「5回失敗したらロックするから大丈夫」という甘い設計に出くわす。だが、攻撃者はその「5回」や「CAPTCHAの裏」を泥臭く突いてくるんだ。
今日のテーマは、ブルートフォース(総当たり攻撃)と認証バイパスに対する、現場レベルの堅牢な防御設計だ。教科書的な「レート制限をかけましょう」の一言で終わらせず、なぜそれが破られるのか、そしてどう実装すれば完封できるのかを話そう。
—
1. 攻撃者が狙う「防御の盲点」
攻撃者は、お前たちが設定した「アカウントロック」や「レート制限」をどうバイパスするかを常に考えている。
- 分散型ブルートフォース(Low and Slow): 1つのIPからではなく、数千のボットネットを使って「1IPあたり1時間に1回」だけ試行する。これだとWAFの閾値にも引っかからないし、ログ監視も「ノイズ」として処理されてしまう。
- パスワードスプレー攻撃: 1つのIDを何度も叩くのではなく、よくあるパスワード(
Password123!など)を数千のIDに対して1回ずつ試す。これならアカウントロックアウトはトリガーされない。 - ヘッダー操作によるバイパス:
X-Forwarded-Forなどのヘッダーを偽装し、レート制限のカウントをリセットさせようとする。
これらに対抗するには、「IPアドレスのみ」に依存した制限は捨てなければならない。
—
2. 実践的防御:Redisを用いた動的レート制限(PHP実装)
IP単位の制限だけでは不十分だ。ユーザーID(またはメールアドレス)をキーにして、バックエンドのインメモリDB(Redis)で厳密に追跡する手法が最も現実的かつ強力だ。
以下のコードは、PHPで「1分間に5回失敗したら、そのユーザーIDを15分間ロックする」というロジックの骨子だ。
<?php
// Redis接続(事前準備)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
function isLoginAllowed($username) {
global $redis;
// ユーザーIDに基づいたキーを作成
$lockKey = "login_lock:" . md5($username);
$attemptKey = "login_attempts:" . md5($username);
// 1. ロック状態を確認
if ($redis->exists($lockKey)) {
return false; // ロック中
}
// 2. 失敗回数が閾値を超えていないか確認
$attempts = (int)$redis->get($attemptKey);
if ($attempts >= 5) {
// 5回失敗したら15分間ロック
$redis->setex($lockKey, 900, 'locked');
return false;
}
return true;
}
function recordFailedAttempt($username) {
global $redis;
$attemptKey = "login_attempts:" . md5($username);
// 失敗回数をインクリメントし、有効期限を1分に設定
$redis->incr($attemptKey);
$redis->expire($attemptKey, 60);
}
?>
—
3. インフラ層での鉄壁の守り:Nginx設定
アプリケーション層に負荷をかける前に、Nginxの limit_req モジュールで「異常なリクエスト」を門前払いする。これはボットによる機械的なアクセスを遮断するのに非常に有効だ。
# nginx.conf の http ブロックに記述
# ユーザーごとのレート制限(IPアドレスベース)
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
# ログインページへのアクセスを保護
location /login {
# 5リクエスト/秒まで許可し、バーストは許可しない
limit_req zone=login_limit burst=0 nodelay;
# ここにプロキシ設定などを記述
proxy_pass http://backend_app;
}
この設定の肝は burst=0 だ。一度に大量のアクセスを叩き込む「スパイク攻撃」を許容しない設定にしている。
—
4. セキュリティチーフからのアドバイス:ログと監視
どれだけ堅牢なコードを書いても、攻撃の手法は進化する。「ログイン失敗ログ」をただファイルに書き出すだけでは意味がない。以下の3点は必ず実施してほしい。
1. 異常な失敗パターンを可視化する: 「特定のユーザーIDが短期間で複数のIPから試行されている」という相関関係を監視ツールのダッシュボードで追えるようにする。
2. 成功時も監視する: 「異常に成功率の低いIDリスト」を生成する。これは、リスト型攻撃(パスワードリスト攻撃)を受けている可能性が高い兆候だ。
3. CAPTCHAの適切な運用: reCAPTCHA v3 のような、ユーザーにストレスを与えない(スコアベースの)認証を導入し、怪しいスコアが返ってきた場合のみ、2要素認証(MFA)を強制するフローを設計すること。
まとめ
セキュリティは「点」ではなく「面」で守るものだ。
アプリ側でレート制限をかけ、インフラ層でバーストを抑え、ログで異常を検知する。この多層防御こそが、現場の泥臭い攻撃を退ける唯一の道だ。
コードをコピペして終わりにするな。お前のシステムがどのような脅威にさらされているか、ログを見て想像するんだ。それができるエンジニアこそが、最強のエンジニアだ。
コメント