こんにちは!セキュリティの現場を渡り歩いてきたホワイトハッカーの私ですが、今日は少し目線を変えて、新人のIT担当者や開発者の皆さんに向けたお話をしたいと思います。
セキュリティと聞くと、難解な暗号アルゴリズムや、映画に出てくるようなハッキング画面を想像するかもしれません。でも、実はもっと身近なところから守る仕組みが大切なんです。今回は、ウェブサイトの「玄関」を守るためのレートリミット(リクエスト制限)と、総当たり攻撃(ブルートフォース攻撃)への対策について、身近な例えを交えながら一緒に紐解いていきましょう!
—
1. 家の鍵と泥棒に例える「ブルートフォース攻撃」
皆さんの家には玄関の鍵がありますよね。では、もし「泥棒があなたの家の鍵を開けようと、考えうるすべての鍵の組み合わせを試しながら、ガチャガチャと一晩中ドアノブを回し続けたら」どうでしょうか?
これが、サイバー世界におけるブルートフォース攻撃(総当たり攻撃)そのものです。
攻撃者は、ユーザー名(例えば admin など)を固定し、パスワードを「123456」から順に、何百万通りも自動プログラム(ボット)を使って高速でログイン画面に入力し続けます。もし、あなたのサイトのログイン画面に「何回間違えても怒られない」という甘い設定があったらどうでしょう? 泥棒はいつか必ず、あなたの家の正しい鍵(パスワード)を当ててしまいますよね。
ここで必要になるのが、「この人、さっきから何度も鍵穴に違う鍵を差し込んでいるぞ?」と気づいて追い払う仕組みです。それが今回解説するレートリミットになります。
—
2. レートリミット(回数制限)ってなに?
レートリミットとは、一言で言うと「短時間に何度も同じ作業をさせないための交通整理(または入場制限)」です。
身近な例で言えば、遊園地のアトラクションの行列や、銀行のATMでの暗証番号の入力ミス制限が分かりやすいかもしれません。ATMで暗証番号を3回間違えると、カードがガチャンとロックされて使えなくなりますよね。あれも立派なレートリミットの一種です。
ウェブの世界でも同じように、
- 「このIPアドレスからは、1分間に5回までしかログイン試行を許可しない」
- 「同じユーザー名への失敗が5回続いたら、15分間ロックアウトする」
といったルールを設けることで、ボットによる高速な総当たり攻撃を物理的に不可能(あるいは非現実的なほど時間がかかる状態)にしてしまいます。一歩ずつ、こうした対策を実装する方法を学んでいきましょう!
—
3. 実装の現場:コードで見るレートリミットの仕組み
「理屈は分かったけれど、実際にどうやってコードを書けばいいの?」と思いますよね。
ご安心ください。ここではPHPを例に、サーバー側でどのようにリクエストの回数を数えて制限しているのか、シンプルなサンプルコードを見てみましょう。
実際の現場では、こうしたカウンターの管理には高速にデータを読み書きできる Redis などのインメモリデータベースが使われますが、今回はイメージしやすいようにセッションを使った基本形をご紹介します。
<?
// ログイン試行のレートリミットを実装するサンプルコード
// セッションを開始(訪問者を識別するため)
session_start();
// 失敗回数を保存するセッションキー
$failure_key = 'login_failure_count';
// ロックアウトが解除される時間を保存するキー
$lockout_key = 'lockout_until';
// 1. ロックアウト中かどうかをチェック
if (isset($_SESSION[$lockout_key]) && time() < $_SESSION[$lockout_key]) {
// まだロック時間が残っている場合
$remaining_time = $_SESSION[$lockout_key] - time();
header('HTTP/1.1 429 Too Many Requests');
echo "セキュリティ保護のため、アカウントは一時的にロックされています。あと " . $remaining_time . " 秒後に再試行してください。";
exit;
}
// 2. ログインボタンが押されたときの処理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_user = $_POST['username'] ?? '';
$input_pass = $_POST['password'] ?? '';
// 【擬似コード】ここでデータベースのユーザー認証を行うと仮定
$is_authenticated = ($input_user === 'admin' && $input_pass === 'correct_password_123');
if ($is_authenticated) {
// ログイン成功時は失敗カウンターをリセット
unset($_SESSION[$failure_key]);
unset($_SESSION[$lockout_key]);
echo "ログイン成功しました!ようこそ。";
exit;
} else {
// ログイン失敗時の処理
if (!isset($_SESSION[$failure_key])) {
$_SESSION[$failure_key] = 0;
}
$_SESSION[$failure_key]++;
// 失敗回数が「3回」を超えた場合のペルティエ(ロックアウト)設定
$max_attempts = 3;
if ($_SESSION[$failure_key] >= $max_attempts) {
// 60秒間(1分間)アクセスを完全に遮断
$_SESSION[$lockout_key] = time() + 60;
echo "ログインに連続して失敗したため、1分間ロックされました。";
} else {
$remaining_attempts = $max_attempts - $_SESSION[$failure_key];
echo "ユーザー名またはパスワードが間違っています。あと " . $remaining_attempts . " 回失敗するとロックされます。";
}
}
}
?>
<!-- シンプルなログインフォーム -->
<form method="POST" action="">
<label>ユーザー名:</label><br>
<input type="text" name="username" required><br><br>
<label>パスワード:</label><br>
<input type="password" name="password" required><br><br>
<button type="submit">ログイン</button>
</form>
このコードでは、ユーザーがログインに3回失敗すると、自動的に 429 Too Many Requests というステータスコードを返し、一定時間(ここでは60秒)ログインをできなくしています。
—
4. レスポンスとセキュリティヘッダーの重要性
先ほどのコードで 429 Too Many Requests という見慣れない数字が出てきましたね。これは、HTTPステータスコードの一つで、「おっと、ちょっとリクエストの回数が多すぎますよ(落ち着いてください)」とサーバーがクライアントに伝えるためのものです。
さらに、実務のインフラ構築(NginxやApache、あるいはCloudflareなどのWAF)では、リクエスト制限にひっかかったユーザーに対して、次のようなカスタムヘッダーを返すことがよくあります。
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: text/plain; charset=utf-8
Rate limit exceeded. Please try again in 60 seconds.
Retry-Afterヘッダーには、「何秒後に再度アクセスしていいか」という具体的な数字を入れます。行儀の良いボットやAPIクライアントであれば、この数字を見て「じゃあ60秒待つか」と自主的にリクエストを止めてくれます。
—
5. 現場のホワイトハッカーから新人の皆さんへ
レートリミットやブルートフォース対策は、一見地味な機能に見えるかもしれません。「ちゃんと動く機能」を作ることに比べると、エラー画面の調整や制限値のチューニングは面倒くさいと感じることもあるでしょう。
しかし、セキュリティの現場にいる私から言わせれば、ログイン画面にレートリミットがないシステムは、「鍵の開いた玄関に『ご自由にどうぞ』と書いているようなもの」です。
インフラエンジニアやフロントエンド、バックエンドを問わず、ウェブに関わるすべてのエンジニアがこの基本を知っているだけで、世の中の不正アクセス被害は劇的に減ります。ぜひ、ご自身が今関わっているプロジェクトのログイン画面やAPIエンドポイントを見直して、「おや、ここは何度も連打できちゃうな」と思ったら、一歩ずつ対策を組み込んでいってくださいね。
あなたの書いたコードが、今日もユーザーの大切なデータを守る強固な盾になりますように!
コメント