原創作者:Fazx
引言
西湖論劍杯線上預選賽線上賭場一題,明文攻擊出來的hint中給了/flag/seed.txt
以及一個字符串code
,這裏需要稍微腦洞一點想到seed是指隨機數種子,以及Web頁面上的code值是每小時更換的"隨機數":
我們利用php_mt_seed
工具(C編寫,速度很快)可以根據隨機數碰撞出隨機數的種子,從而獲取flag:
以下是原理分析:
隨機數的安全缺陷
隨機數廣泛應用於生成驗證碼、Token、密鑰等場景中,分爲真隨機數和僞隨機數。我們通過算法(常用線性同餘)和種子(常用時鐘)得到的隨機數屬於僞隨機數:當知道種子或已產生的隨機數時,隨機數序列是可以被預測的。
可以看到PHP Manual其實提示了生成隨機數用於加密是不安全的,但是這個Caution不知爲何只存在於英文版的PHP Manual中,中文版被遺漏了…這可能也是很多國內的開發應用出現過此缺陷的一個原因。
PHP中生成隨機數的函數有rand()
和mt_rand()
,它們分別對應srand()
和mt_strand()
兩個用於播種隨機數種子的函數。我們建立rand.php
進行測試:
<?php
mt_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";
}
?>
執行:
可以看出當隨機數種子相同時,不管是rand()
還是mt_rand()
產生的隨機數序列都是相同的,如果seed泄露則會導致隨機數序列的泄露。當種子值爲固定如mt_srand(1000)
時,隨機數形同虛設;而使用動態種子也未必安全,如:
//seed值較小,直接遍歷爆破
mt_srand(mt_rand(0,1000));
//用公開的time()作爲種子,和靜態種子一樣危險
mt_srand(time());
//破解時要注意服務器時間可能存在偏差,需要設定一個較小的範圍
自PHP 4.2.0 起,隨機數發生器會自動完成播種,不再需要手工調用srand()
或mt_srand()
,但是這樣仍舊不安全,我們分別對兩個函數進行討論:
rand()
rand()
在產生隨機數時不會自動調用srand()
,產生的隨機數序列可以通過這個式子預測:
state[i] = state[i-3] + state[i-31]
所以我們可以收集rand()
生成的32位以上的隨機序列,以預測後面的隨機序列。
詳細參考:Cracking-Php-Rand
並且在某些平臺下rand()
最大值爲32767,非常容易遭到爆破。
mt_rand()
根據PHP Manual,mt_rand()
產生隨機數值的平均速度比libc提供的rand()
快四倍,rand()
函數默認使用 libc 隨機數發生器,mt_rand()
函數是非正式用來替換它的。
mt_rand()
函數的安全缺陷主要出現在,所謂"自動播種"其實是PHP在同一個請求進程中只會進行一次播種,也就是說即使多次調用mt_rand()
函數,也只會根據第一次播種的種子生成隨機數。這一結論的證明可以通過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) );
}
由於根據種子生成隨機數序列的計算並不可逆,有效的破解方法應該是窮舉種子並生成隨機數序列,與已知的隨機數(序列)作比較,這也是文章開頭提到的php_mt_seed
工具的實現邏輯。
安全建議
涉及到加密/權限/CSRF Token等敏感操作時:
- 不要使用時間函數作爲種子或直接作爲隨機數:
time()
/microtime()
- 不要直接使用
rand()``mt_rand()
這樣的弱僞隨機數生成器 - 隨機數要足夠長以防禦暴力破解
相關題目學習
參考鏈接:
http://wonderkun.cc/index.html/?p=585