mt_rand() は mersennetwister アルゴリズムを使用してランダムな整数を返します。これは誰もが知っていますが、次の記事では主に PHP の mt_rand() 乱数のセキュリティに関する関連情報を紹介しています。参考までに、以下のエディタで学習してみましょう。
前書き
私は少し前に mt_rand() に関連する多くのセキュリティ脆弱性を発見しました。それらは基本的に乱数の使用法に対する誤解が原因でした。ここで、PHP 公式 Web サイトのマニュアルのもう 1 つの落とし穴について言及したいと思います。mt_rand() の概要を見てください。英語版には、余分な黄色の注意警告があることがわかります。
This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. If you need a cryptographically secure value, consider using random_int(), random_bytes(), or openssl_random_pseudo_bytes() instead.
擬似乱数
mt_rand() は、実際には、ほとんどのプログラミング言語の乱数関数は擬似乱数を生成します。真の乱数と擬似乱数の違いについてはここでは説明しません。擬似乱数はシード (一般的に使用されるクロック) によって決定可能な関数 (一般的に使用される) によって生成されることを少しだけ理解する必要があります。 )。これは、シード、つまり生成された乱数を知っていれば、次の乱数列に関する情報を得ることができる (予測可能性) ことを意味します。
mt_rand() の内部で乱数を生成する関数が次のとおりであると単純に仮定します。
ここで、seed は乱数シード、i はこの乱数関数が呼び出される回数です。 i と rand の 2 つの値が同時にわかると、seed の値を簡単に計算できます。たとえば、rand=21 と i=2 を関数 21=seed+(2*10) に代入すると、seed=1 が得られます。非常に簡単ではないでしょうか。シードを取得したら、i が任意の値の場合の rand の値を計算できます。rand = seed+(i*10)
PHP の自動シード
前のセクションから、mt_rand() が呼び出されるたびに、シードと現在の呼び出し回数 i に基づいて擬似乱数が計算されることがすでにわかりました。そして、シードは自動的にシードされます: 注: PHP 4.2.0 以降、乱数ジェネレーターのシードに srand() または mt_srand() を使用する必要はありません。これは、乱数ジェネレーターのシードがシステムによって自動的に行われるようになったためです。
それでは、システムはいつシードを自動的に完了するのかという疑問が生じます。mt_rand() が毎回自動的にシードされるのであれば、シードをクラックする意味はありません。マニュアルにはこの点に関する詳細な情報は記載されていません。インターネット中を検索しましたが、信頼できる答えが見つからなかったので、ソース コードを確認する必要がありました ^mtrand:
PHPAPI void php_mt_srand(uint32_t seed) { /* Seed the generator with a simple uint32 */ php_mt_initialize(seed, BG(state)); php_mt_reload(); /* Seed only once */ BG(mt_rand_is_seeded) = 1; } /* }}} */ /* {{{ php_mt_rand */ PHPAPI uint32_t php_mt_rand(void) { /* Pull a 32-bit integer from the generator state Every other access function simply transforms the numbers extracted here */ register uint32_t s1; if (UNEXPECTED(!BG(mt_rand_is_seeded))) { php_mt_srand(GENERATE_SEED()); } if (BG(left) == 0) { php_mt_reload(); } --BG(left); s1 = *BG(next)++; s1 ^= (s1 >> 11); s1 ^= (s1 << 7) & 0x9d2c5680U; s1 ^= (s1 << 15) & 0xefc60000U; return ( s1 ^ (s1 >> 18) ); }mt_rand() が呼び出されるたびに、最初に、播種されました。播種されている場合は直接乱数を生成し、そうでない場合は php_mt_srand を呼び出して播種します。つまり、各 php CGI プロセス中、mt_rand() への最初の呼び出しのみが自動的にシードされます。次に、この最初に蒔かれたシードに基づいて乱数が生成されます。 php のいくつかの動作モードのうち、CGI を除く (各リクエストは CGI プロセスを開始し、リクエストが完了するとプロセスを閉じます。php.ini 環境変数は毎回再読み込みする必要があるため、効率が低くなります。基本的に、1 つのプロセスがリクエストを処理した後、スタンバイは次のリクエストを待ち、複数のリクエストが処理されるまでリサイクルされません (タイムアウト後にもリサイクルされます)。 scriptをテストするスクリプトを作成してください。それをテストしてください結果:(Windows+phpstudy)
apache 1000リクエスト
nginx500リクエスト1 つのプロセスで処理されるリクエストの数。自動シード処理についての結論を確認してみましょう:
<?php //pid.php echo getmypid();
<?php //test.php $old_pid = file_get_contents('http://localhost/pid.php'); $i=1; while(true){ $i++; $pid = file_get_contents('http://localhost/pid.php'); if($pid!=$old_pid){ echo $i; break; } }
<?php //pid1.php if(isset($_GET['rand'])){ echo mt_rand(); }else{ echo getmypid(); }pid によって判断され、新しいプロセスが開始されると、次のいずれかの mt_rand をランダムに取得します。 2 ページ ( ) の出力:
<?php //pid2.php echo mt_rand();
最初の乱数 1513334371 を取得してシードを爆発させます:
<?php //test.php $old_pid = file_get_contents('http://localhost/pid1.php'); echo "old_pid:{$old_pid}\r\n"; while(true){ $pid = file_get_contents('http://localhost/pid1.php'); if($pid!=$old_pid){ echo "new_pid:{$pid}\r\n"; for($i=0;$i<20;$i++){ $random = mt_rand(1,2); echo file_get_contents("http://localhost/pid".$random.".php?rand=1")." "; } break; } }
3 つの可能なシードを展開します。数は非常に小さいので、1 つずつ手動でテストします。
old_pid:972 new_pid:7752 1513334371 2014450250 1319669412 499559587 117728762 1465174656 1671827592 1703046841 464496438 1974338231 46646067 981271768 1070717272 571887250 922467166 606646473 134605134 857256637 1971727275 2104203195出力:
最初の 20 桁は上記のスクリプトで取得したものとまったく同じです。 確認されたシードは 1513334371 です。シードを使用すると、mt_rand() を何度でも呼び出して生成される乱数を計算できます。たとえば、このスクリプトでは 21 桁を生成し、最後の桁は 1515656265 です。スクリプトを実行した後にサイトにアクセスしていない場合は、http://localhost/pid2.php を開くと同じ 1515656265 が表示されます。
それで、私たちは次の結論に達しました:
php の自動シードは、php CGI プロセスで初めて mt_rand() が呼び出されたときに発生します。訪問したページに関係なく、リクエストが同じプロセスで処理される限り、最初に自動的に播種された同じシードを共有します。
乱数の生成が特定の関数に依存していることはすでにわかっています。rand = seed+(i*10)
。对于这样一个简单的函数,我们当然可以直接计算(口算)出一个(组)解来,但 mt_rand() 实际使用的函数可是相当复杂且无法逆运算的。有效的破解方法其实是穷举所有的种子并根据种子生成随机数序列再跟已知的随机数序列做比对来验证种子是否正确。php_mt_seed^phpmtseed就是这么一个工具,它的速度非常快,跑完2^32位seed也就几分钟。它可以根据单次mt_rand()的输出结果直接爆破出可能的种子(上面有示例),当然也可以爆破类似mt_rand(1,100)
これは MIN MAX 出力のシードを制限します (以下の例で役立ちます)。
セキュリティの問題
ここまで言ってきましたが、なぜ乱数は安全ではないのでしょうか?実際、機能自体には何の問題もありませんが、生成された乱数をセキュリティ暗号化の目的で使用してはならないと公式にも明記されています(ただし、中国語版のマニュアルにはそのことは書かれていません)。問題は、開発者がこれが真の乱数ではないことに気づいていないことです。既知の乱数シーケンスからシードを爆発させることができることはすでにわかっています。つまり、どのページにも出力乱数またはその派生値(可逆乱数値)が存在する限り、他のページにある乱数は「乱数」ではなくなるのです。乱数を出力する一般的な例には、検証コード、ランダムなファイル名などが含まれます。一般的な乱数は、暗号化キーなどのパスワード検証値の取得などのセキュリティ検証に使用されます。理想的な攻撃シナリオ:
真夜中に、Apache (nginx) がすべての PHP プロセスを取り戻すのを待ちます (次回の訪問時に確実に再シードされるようにするため)。検証コード ページに 1 回アクセスし、コードを逆にします。検証コードの文字に基づいて乱数を生成し、乱数に基づいてブラストして乱数シードを生成します。次に、パスワード取得ページにアクセスすると、生成されたパスワード取得リンクは乱数に基づいています。このリンクを簡単に計算して、管理者のパスワードを取得できます...XXOO
例
PHPCMS MT_RAND SEED CRACK により認証キー漏洩が発生しました Yuniu は私よりも上手に書いています、彼の
Discuz x3.2 認証キー漏洩を見てくださいこれは実際に似ています。公式パッチが公開されているので、興味のある方はご自身で解析してみてください。
概要
以上がPHP における mt_rand() 乱数セキュリティの深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。