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() は、実際には、ほとんどのプログラミング言語の乱数関数は擬似乱数を生成します。ここでは、真の乱数と擬似乱数の違いについては説明しません。擬似乱数は、シード (通常は線形合同) によって生成される擬似乱数です。中古時計)。これは、シード、つまり生成された乱数を知っていれば、次の乱数列に関する情報を得ることができる (予測可能性) ことを意味します。
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 つのプロセスがリクエストを処理した後、スタンバイは次のリクエストを待ち、複数のリクエストが処理されるまでリサイクルされません (タイムアウト後にもリサイクルされます)。
テストするスクリプトを書く
<?php //pid.php echo getmypid();テスト結果: (windows+phpstudy)apache 1000リクエストnginx 500リクエストもちろん、このテストはApacheとng inxa process 処理できるリクエストの数。自動シードに関する先ほどの結論を確認してみましょう:
<?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(); }
<?php //pid2.php echo mt_rand();pidで判断すると、新しいプロセスが開始されると、2つのページが表示されます。ランダムに取得された mt_rand() の 1 つの出力:
<?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; } }最初の乱数 1513334371 を取得してシードを爆発させます:
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 21042031953 つの可能なシードを展開します。数は非常に少なく、手動でテストされます。 by one :
smldhz@vm:~/php_mt_seed-3.2$ ./php_mt_seed 1513334371 Found 0, trying 704643072 - 738197503, speed 28562751 seeds per second seed = 735487048 Found 1, trying 1308622848 - 1342177279, speed 28824291 seeds per second seed = 1337331453 Found 2, trying 3254779904 - 3288334335, speed 28811010 seeds per second seed = 3283082581 Found 3, trying 4261412864 - 4294967295, speed 28677071 seeds per second Found 3出力: 最初の 20 個の数値は、上記のスクリプトで取得されたものとまったく同じです。確認されたシードは 1513334371 です。シードを使用すると、mt_rand() を何度でも呼び出して生成される乱数を計算できます。たとえば、このスクリプトでは 21 桁を生成し、最後の桁は 1515656265 です。スクリプトを実行した後にサイトにアクセスしていない場合は、http://localhost/pid2.php を開くと同じ 1515656265 が表示されます。 それで、私たちは次の結論に達しました: php の自動シードは、php CGI プロセスで初めて mt_rand() が呼び出されたときに発生します。訪問したページに関係なく、リクエストが同じプロセスで処理されている限り、最初に自動的に播種された同じシードを共有します。 php_mt_seed
乱数の生成が特定の関数に依存することはすでにわかっています。 セキュリティの問題 ここまで言いましたが、なぜ乱数は安全ではないのでしょうか?実際、機能自体には何の問題もありませんが、生成された乱数をセキュリティ暗号化の目的で使用してはならないと公式にも明記されています(ただし、中国語版のマニュアルにはそのことは書かれていません)。問題は、開発者がこれが真の乱数ではないことに気づいていないことです。既知の乱数シーケンスからシードを爆発させることができることはすでにわかっています。つまり、どのページにも出力乱数またはその派生値(可逆乱数値)が存在する限り、他のページにある乱数は「乱数」ではなくなるのです。乱数を出力する一般的な例には、検証コード、ランダムなファイル名などが含まれます。一般的な乱数は、暗号化キーなどのパスワード検証値の取得などのセキュリティ検証に使用されます。理想的な攻撃シナリオ: 真夜中に、Apache (nginx) がすべての PHP プロセスを取り戻すのを待ちます (次回の訪問時に確実に再シードされるようにするため)。検証コード ページに 1 回アクセスし、コードを逆にします。検証コードの文字に基づいて乱数を生成し、乱数に基づいてブラストして乱数シードを生成します。次に、パスワード取得ページにアクセスすると、生成されたパスワード取得リンクは乱数に基づいています。このリンクを簡単に計算して、管理者のパスワードを取得できます... XXOO 例 PHPCMS MT_RAND SEED CRACKが認証キー漏洩を引き起こしています Yuniuは私よりも上手に書いています、彼の Discuz x3.2認証キーが漏洩していることを見てくださいこれは実際に似ています。公式パッチが公開されているので、興味のある方はご自身で解析してみてください。 rand = seed+(i*10)
。对于这样一个简单的函数,我们当然可以直接计算(口算)出一个(组)解来,但 mt_rand() 实际使用的函数可是相当复杂且无法逆运算的。有效的破解方法其实是穷举所有的种子并根据种子生成随机数序列再跟已知的随机数序列做比对来验证种子是否正确。php_mt_seed^phpmtseed就是这么一个工具,它的速度非常快,跑完2^32位seed也就几分钟。它可以根据单次mt_rand()的输出结果直接爆破出可能的种子(上面有示例),当然也可以爆破类似mt_rand(1,100)
これにより、MIN MAX 出力のシードが制限されます (以下の例で使用されます)。
以上がPHP における mt_rand() 乱数のセキュリティについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。