首頁 後端開發 php教程 PHP中mt_rand()隨機數的安全實例詳解

PHP中mt_rand()隨機數的安全實例詳解

Jan 06, 2018 pm 04:09 PM
php rand 隨機數

在前段時間挖了不少跟mt_rand()相關的安全漏洞,基本上都是錯誤理解隨機數用法導致的。這裡又要提一下php官網manual的一個坑,看下關於mt_rand()的介紹:中文版^cn 英文版^en,可以看到英文版多了一塊黃色的 Caution 警告。 mt_rand()使用mersennetwister演算法傳回隨機整數,這個大家都知道,但下面這篇文章主要給大家介紹的是關於PHP中mt_rand()隨機數安全的相關資料,文中介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。

很多國內開發者估計都是看的中文版的介紹而在程式中使用了mt_rand()來產生安全令牌、核心加解密key等等導致嚴重的安全問題。

偽隨機數

mt_rand()並不是一個真·隨機數產生函數,實際上絕大多數程式語言中的隨機數函數所產生的都是偽隨機數。關於真隨機數和偽隨機數的區別這裡不展開解釋,只需要簡單了解一點

偽隨機是由可確定的函數(常用線性同餘),透過一個種子(常用時鐘),產生的偽隨機數。這意味著:如果知道了種子,或已經產生的隨機數,都可能獲得接下來隨機數序列的資訊(可預測性)。

簡單假設 mt_rand()內部產生隨機數的函數為: rand = seed+(i*10) 其中 seed 是隨機數種子, i 是第幾次呼叫這個隨機數字函數。當我們同時知道 i 和 rand 兩個值的時候,就能很容易的算出seed的值來。例如 rand=21 , i=2 代入函數 21=seed+(2*10) 得到 seed=1 。是不是很簡單,拿到seed之後,就能計算出當 i 為任意值時候的 rand 的值了。

PHP的自動播種

從上一節我們已經知道每一次mt_rand()被呼叫都會根據seed和目前呼叫的次數i來計算出一個偽隨機數。而且seed是自動播種的:

Note: 自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 給隨機數產生器播種 ,因為現在是由系統自動完成的。

那麼問題就來了,到底系統自動完成播種是在什麼時候,如果每次呼叫mt_rand()都會自動播種那麼破解seed也就沒意義了。關於這一點manual並沒有給出詳細資訊。網路上找了一圈也沒可靠的答案 只能去翻原始碼^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 環境變量等導致效率低下,現在用的應該不多了)以外,基本上都是一個進程處理完請求之後standby等待下一個,處理多個請求之後才會回收(超時也會回收)。

寫個腳本測試一下

<?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;
 }
}
登入後複製

測試結果:(windows+phpstudy)

apache 1000請求

nginx 500請求

#當然這個測試只是確認了apache和nginx一個進程可以處理的請求數,再來驗證一下剛才關於自動播種的結論:

<?php
//pid1.php
if(isset($_GET['rand'])){
 echo mt_rand();
}else{
 echo getmypid();
}
登入後複製
<?php
//pid2.php
echo mt_rand();
登入後複製
<?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;
 }
}
登入後複製

透過pid來判斷,當新進程開始的時候,隨機獲取兩個頁面其中一個的mt_rand() 的輸出:

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
登入後複製

拿第一個隨機數1513334371 去爆破種子:

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
登入後複製

爆破出了3個可能的種子,數量很少手動一個一個測試:

<?php
mt_srand(735487048);//手工播种
for($i=0;$i<21;$i++){
 echo mt_rand()." ";
}
登入後複製

輸出:

前20位元跟上面腳本取得的一模一樣,確認種子就是1513334371 。有了種子我們就能計算出任意次數呼叫mt_rand()產生的隨機數了。例如這個腳本我產生了21位,最後一位是 1515656265 如果跑完剛才的腳本之後沒訪問過站點,那麼打開 http://localhost/pid2.php 就能看到相同的 1515656265 。

所以我們得到結論:

php的自動播種發生在php cgi行程中第一次呼叫mt_rand()的時候。跟造訪的頁面無關,只要是同一個行程處理的請求,就會共用同一個原本自動播種的種子。

php_mt_seed

我們已經知道隨機數的產生是依賴特定的函數,上面曾經假設為 rand = seed+(i*10)  。對於這樣一個簡單的函數,我們當然可以直接計算(口算)出一個(組)解來,但 mt_rand() 實際使用的函數可是相當複雜且無法逆運算的。有效的破解方法其實是窮舉所有的種子並根據種子生成隨機數序列再跟已知的隨機數序列做比對來驗證種子是否正確。 php_mt_seed^phpmtseed就是這麼一個工具,它的速度非常快,跑完2^32位元seed也就幾分鐘。它可以根據單次mt_rand()的輸出結果直接爆破出可能的種子(上面有範例),當然也可以爆破類似mt_rand(1,100)這樣限定了MIN MAX輸出的種子(下面實例中有用到)。

安全性問題

說了這麼多,那到底隨機數字怎麼不安全了呢?其實函數本身沒有問題,官方也明確提示了產生的隨機數字不應用於安全加密用途(雖然中文版manual沒寫)。問題在於開發者並沒有意識到這並不是一個 真·隨機數 。我們已經知道,透過已知的隨機數序列可以爆破出種子。也就是說,只要任意頁面中存在輸出隨機數或其衍生值(可逆推隨機值),那麼其他任意頁面的隨機數將不再是「隨機數」。常見的輸出隨機數的例子例如驗證碼,隨機檔案名稱等等。常見的隨機數字用於安全驗證的例如找回密碼校驗值,例如加密key等等。一個理想中的攻擊場景:

夜深人靜,等待apache(nginx)收回所有php進程(確保下次訪問會重新播種),訪問一次驗證碼頁面,根據驗證碼字元逆推出隨機數,再根據隨機數爆破出隨機數種子。接著造訪找回密碼頁面,產生的找回密碼連結是基於隨機數的。我們就可以輕鬆計算出這個連結,找回管理員的密碼………XXOO

實例

PHPCMS MT_RAND SEED CRACK致authkey洩漏雨牛寫的比我好,看他的就夠了

Discuz x3.2 authkey洩漏這個其實也差不多。官方已出補丁,有興趣的可以自己去分析一下。

相關推薦:

PHP的真偽隨機數

#php隨機數產生的rand()函數

JavaScript實作隨機數去重新產生器的實例

#

以上是PHP中mt_rand()隨機數的安全實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也稱為 VS Code,是一個免費的原始碼編輯器 - 或整合開發環境 (IDE) - 可用於所有主要作業系統。 VS Code 擁有大量針對多種程式語言的擴展,可以輕鬆編寫

我後悔之前不知道的 7 個 PHP 函數 我後悔之前不知道的 7 個 PHP 函數 Nov 13, 2024 am 09:42 AM

如果您是經驗豐富的PHP 開發人員,您可能會感覺您已經在那裡並且已經完成了。操作

您如何在PHP中解析和處理HTML/XML? 您如何在PHP中解析和處理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

See all articles