原創 Fazx 合天智匯

1、引言

西湖論劍杯線上預選賽線上賭場一題,明文攻擊出來的hint中給了/flag/seed.txt以及一個字符串code,這裏需要稍微腦洞一點想到seed是指隨機數種子,以及Web頁面上的code值是每小時更換的"隨機數":

PHP中的隨機數安全問題

我們利用phpmtseed工具(C編寫,速度很快)可以根據隨機數碰撞出隨機數的種子,從而獲取flag:

PHP中的隨機數安全問題

以下是原理分析:

2、隨機數的安全缺陷

隨機數廣泛應用於生成驗證碼、Token、密鑰等場景中,分爲真隨機數和僞隨機數。我們通過算法(常用線性同餘)和種子(常用時鐘)得到的隨機數屬於僞隨機數:當知道種子或已產生的隨機數時,隨機數序列是可以被預測的。

PHP中的隨機數安全問題

可以看到PHP Manual其實提示了生成隨機數用於加密是不安全的,但是這個Caution不知爲何只存在於英文版的PHP Manual中,中文版被遺漏了...這可能也是很多國內的開發應用出現過此缺陷的一個原因。

PHP中生成隨機數的函數有rand()和mtrand(),它們分別對應srand()和mtstrand()兩個用於播種隨機數種子的函數。我們建立rand.php進行測試:

<?phpmt_srand(2333);srand(2333);echo "seed=2333,rand()產生的隨機數序列:n";for($i=1;$i<=3;$i++){ echo rand()."n";}echo "seed=2333,mt_rand()產生的隨機數序列:n";for($i=1;$i<=3;$i++){ echo mt_rand()."n";}?>

執行:

PHP中的隨機數安全問題

可以看出當隨機數種子相同時,不管是rand()還是mtrand()產生的隨機數序列都是相同的,如果seed泄露則會導致隨機數序列的泄露。當種子值爲固定如mtsrand(1000)時,隨機數形同虛設;而使用動態種子也未必安全,如:

//seed值較小,直接遍歷爆破mt_srand(mt_rand(0,1000));//用公開的time()作爲種子,和靜態種子一樣危險mt_srand(time());//破解時要注意服務器時間可能存在偏差,需要設定一個較小的範圍

自PHP 4.2.0 起,隨機數發生器會自動完成播種,不再需要手工調用srand()或mt_srand(),但是這樣仍舊不安全,我們分別對兩個函數進行討論

3、rand()

rand()在產生隨機數時不會自動調用srand(),產生的隨機數序列可以通過這個式子預測:

state[i] = state[i-3] + state[i-31]

所以我們可以收集rand()生成的32位以上的隨機序列,以預測後面的隨機序列。

詳細參考:Cracking-Php-Rand

(http://www.sjoerdlangkemper.nl/2016/02/11/cracking-php-rand/)

並且在某些平臺下rand()最大值爲32767,非常容易遭到爆破。

4、mt_rand()

根據PHP Manual,mtrand()產生隨機數值的平均速度比libc提供的rand()快四倍,rand() 函數默認使用 libc 隨機數發生器,mtrand() 函數是非正式用來替換它的。

mtrand()函數的安全缺陷主要出現在,所謂"自動播種"其實是PHP在同一個請求進程中只會進行一次播種,也就是說即使多次調用mtrand()函數,也只會根據第一次播種的種子生成隨機數。這一結論的證明可以通過mt_rand()的源碼分析或寫個小腳本測試來完成,不再展開,函數的核心實現代碼是這一部分:

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) );}

由於根據種子生成隨機數序列的計算並不可逆,有效的破解方法應該是窮舉種子並生成隨機數序列,與已知的隨機數(序列)作比較,這也是文章開頭提到的phpmtseed工具的實現邏輯。

5、安全建議

涉及到加密/權限/CSRF Token等敏感操作時:

  • 不要使用時間函數作爲種子或直接作爲隨機數:time()/microtime()
  • 不要直接使用rand()``mt_rand()這樣的弱僞隨機數生成器
  • 隨機數要足夠長以防禦暴力破解

    6、相關題目學習

    0CTF 2016 Rand2:http://www.vuln.cn/6004

    NJCTF 2017 Guess:

    https://www.cnblogs.com/afanti/p/8722760.html

    7、相關操作訓練

    PHP常見危險函數:瞭解PHP常見的危險函數,使用這些函數可能帶來的程序脆弱性;

    PHP中的隨機數安全問題

    參考鏈接:http://wonderkun.cc/index.html/?p=585

  • 相關文章