1. 程式人生 > >php隨機函式mt_rand()產生的小問題大漏洞

php隨機函式mt_rand()產生的小問題大漏洞

**說到隨機函式的應用,作為一個菜鳥,理解的也不是很深刻,在這裡之作為一個筆記來記錄,以後慢慢將其掌握之後,再在內容上面進行加深。

隨機函式的作用,常常是用來生成驗證碼、隨機檔名、訂單號,如果用來做安全驗證的話常常用來生成加密key、token等等。**

一、常見的隨機函式

1、rand()
常用的隨機函式,預設生成0-getrandmax()之間的隨機數,不過因為效能問題,已經被mt_rand()函式替代
相關函式:
rand(int $min,int $max)
srand(int $seed),生成時間種子,同一個時間種子下隨機生成的隨機數值是相同的。
getrandmax()獲取最大隨機數,這裡獲取的隨機數會隨系統的不同而不同。如linux最大2147483647
2、mt_rand


常用的隨機函式,預設生成0-mt_getrandmax()之間的隨機數, Mersenne Twister 演算法生成隨機整數
相關函式:
mt_srand(),生成種子,同一個種子下隨機生成的隨機數值是相同的。
該函式是產生隨機值的更好選擇,返回結果的速度是 rand() 函式的 4 倍(手冊是是這麼寫的),我個人並不認同的,我感覺他說的4倍是很多年前的事了。因為mt_rand()使用的Mersenne Twister algorythm是1997的事,所以在很多年前,和rand()在速度上的差異可能是(4倍),自2004年,rand()已經開始使用algorythm,所以現在它們速度上沒有太大的區別.
有時候手冊也是騙人的,就像一會要說的這個函式造成的問題。
3、uniqid()

生成唯一ID的函式,精確到了微妙,較mt_rand精確。適用場景生成token和生成uuid
具體細節沒做研究
4、openssl_random_pseudo_bytes()
適用於生成token,具體詳情沒做研究

二、mt_rand()函式造成的問題所在

--
今天重點記錄一下mt_rand帶來的問題,和在CTF中的具體解法。對於這個函式的介紹是有兩個版本的,一個是英文版,一個是中文版,去對比一下,在英文版中會多出一個警告:

Caution: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_srand()函式完成的),當隨機種子生成後,後面生成的隨機數都會根據這個隨機種子生成。所以前面也說到,同一個種子下隨機生成的隨機數值是相同的。同時,也解釋了我們破解隨機種子的可行性。如果每次呼叫mt_rand()函式都需要生成一個隨機種子的話,那根本就沒辦法破解。
做一個簡單的測試,測試隨機數種子相同,後面的每次執行的隨機數也相同
指令碼:

<?php
mt_srand(45678913);
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
?>

php隨機函式mt_rand()產生的小問題大漏洞
兩次執行結果:
php隨機函式mt_rand()產生的小問題大漏洞
可以看到得到的隨機數是相同的。
同時我們應該注意,mt_srand()函式播種的時候,只有在第一次呼叫mt_rand()函式的時候才會使用。所以如果我們知道了第一次生成的隨機數值,就可能爆破出隨機數種子。

--
接下來就來驗證一下
首先爆破出隨機數種子,利用工具php_mt_srand
工具連結:https://github.com/lepiaf/php_mt_seed
這個工具的具體用法,也不再著解釋,說起來還是挺複雜的,在這裡推薦一個文章吧,看完就看一瞭解個大概,同時還有助於理解隨機數

https://×××w.openwall.com/php_mt_seed/README
爆破隨機數種子,我們將得到的第一組數進行爆破
php隨機函式mt_rand()產生的小問題大漏洞
得到三組seed值,裡面就有我們使用的隨機數種子,當然在正常情況下我們是不知道這個數值的,所以還需要去驗證。

--
如何驗證?
1、只需要將這個種子通過mt_srand()函式生成數值後再呼叫幾組mt_rand()函式生成幾組隨機數\
2、然後將隨機數和我們剛開始得到的隨機數對比即可。
測試第一組
php隨機函式mt_rand()產生的小問題大漏洞

php隨機函式mt_rand()產生的小問題大漏洞
測試第二組
php隨機函式mt_rand()產生的小問題大漏洞

php隨機函式mt_rand()產生的小問題大漏洞
測試第三組
php隨機函式mt_rand()產生的小問題大漏洞

php隨機函式mt_rand()產生的小問題大漏洞
可以看到只有第二組的隨機數和原來的相同,到這裡便成功獲取到了seed值。

三、CTF中的題目

題目來自於成都大學網路***演練平臺--隨機數
題目連結:http://ctf.cdusec.org/challenges
題目非常簡單,直接就給出了題目隨機數的原始碼以及前幾組隨機數:

<?php
echo "PHP 5.4.26";
mt_srand(xxxxxxxx);
#We can't tell you what is xxxxxxxx!
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo mt_rand()."<br/>";
echo "echo flag{".mt_rand()."}"
?>
984489752
619387123
2070958802
2105559368
1909473866
1679323715
1910332168
640569646
1103001695
1871111424
flag
So, Please guess the flag!

理解了上面所說的爆破步驟的話,對這個題目簡直太容易了。
1、因為我們不知道××××代表那些數字,但是給出了第一組隨機數。上面也說了,只有第一次呼叫mt_rand()函式的時候才會自動播種,接下來的就會根據這個種子生成隨機數。所以我們來利用第一組爆破
php隨機函式mt_rand()產生的小問題大漏洞
2、將seed數值帶入到指令碼mt_srand()函式當中,去跑flag,因為只要得到seed,接下來不管怎麼跑,跑幾次,得到的幾組數值都是相同的。這裡得到兩組seed值,測試兩次,將得到的flag遞交看一下那個正確即可。
php隨機函式mt_rand()產生的小問題大漏洞

四、應當注意的問題。

隨機數這個東西在系統之間和php版本之間是有削微的區別的,比如就CTF這道題目而言,開始我並沒有注意php版本問題,當時的測試環境是php7的版本,同樣的做法,答案卻是不同的,所以結果也一直出不來。
同時也告訴我們,細節決定成敗。

因為是剛剛學習這一知識點,可能講解的也不夠詳細或者有些許錯誤。哪位大佬看到不準確的希望給指出,謝謝!

相關連結:
http://php.net/manual/en/function.mt-rand.php
http://php.net/manual/zh/function.mt-rand.php