動態

詳情 返回 返回

PHP又出Bug了?md5('240610708')竟然等於 md5('QNKCDZO')! - 動態 詳情

如下圖所示,'240610708''QNKCDZO' 是兩個完全不同的字符串,它們的 MD5 哈希值自然也不相同。可為什麼明明不同,PHP 還會認為這兩個哈希值相等呢?更離譜的是,從 2004 年底的 PHP 4.3.10 版本開始,這個“問題”至今一直存在,所有後續版本都會認為它們是相等的

難道是 PHP 又出 bug 了?還是這背後另有隱情?讓我們一探究竟!

這看起來的確像是 PHP 的一個 bug。但實際上,這只是 == 弱類型比較運算符帶來的副作用。這個副作用的危害在於,只要還在用 == 進行哈希值比較,就無疑會埋下安全隱患!

我們先來看看這兩個本不相同的 MD5 哈希值有什麼特點,

0e462097431906509019562988736854
0e830400451993494058024219903391

不難發現,這兩個哈希值都0e 開頭,並且 0e 之後全是數字

在 PHP 中,對於形如 '0e[0-9]+' 的字符串,PHP 會嘗試將它解析為用科學計數法表示的數字,即:

0e462097431906509019562988736854
  => 0 × 10的462097431906509019562988736854次方 = 0
  
0e830400451993494058024219903391
  => 0 × 10的830400451993494058024219903391次方 = 0

由於 0 乘以任何數都等於 0,所以實際比較的是 0 == 0 嗎?結果當然是 true,這才導致了這個看似 bug 的現象。


再從 PHP 源代碼的角度來看,如果弱類型比較運算符 == 兩邊的值都是字符串,那麼會執行 zendi_smart_strcmp() 這個函數。而這個函數最初要做的,就是試圖通過 is_numeric_str_function() 將字符串轉換成對應的數字,成功轉換成數字後,再對數字進行比較。

對於形如 '0e[0-9]+' 的科學計數法字符串,無論 e 之後是什麼,都會被轉換成浮點數 0,0 自然等於 0 嘍,所以 == 比較的結果為 true


瞭解了不同的 MD5 哈希值會被 PHP 中的 == 判定為相同的原理後,就再來看看這個問題帶來的危害吧。

顯然,這個問題可能導致嚴重的安全漏洞:如果某個系統使用 md5($password . $salt) == $stored_hash 來驗證用户身份,那麼攻擊者就可能找到另一個字符串,即使不是正確的密碼,但加鹽後的 MD5 哈希值滿足 '0e[0-9]+' 的條件,而剛好來自數據庫中的 $stored_hash 也是 0e 開頭之後全是數字,這樣的話攻擊者就可以繞過密碼驗證了。

那如何堵住這個安全漏洞呢?

既然漏洞是由弱類型比較運算符 == 引起的,那最簡單的辦法就是改用 === 進行嚴格比較。而更好的方法是,使用 PHP 5.6+ 提供的專門用於 哈希值比較 的安全函數 hash_equals()。該函數還能通過犧牲性能來防止時序攻擊。其源代碼的註釋中寫道:這是安全性敏感的代碼,千萬別為了追求速度去優化啊!

時序攻擊(timing attack)是一種通過測量代碼執行時間的微小差異來推測機密信息的攻擊方式,比如對於普通的 === 比較,其執行時間會因不一致的字符的出現位置的不同而不同。例如,相較於兩個字符串的第一個字符就不相同,前面的字符全部一致,只有最後一個字符不同,後者的運算時間應該更長。

而更安全的做法是改用 PHP 5.5+ 提供的 password_hash() 函數來生成哈希值,並搭配 password_verify() 函數進行校驗,而不要使用 MD5 進行安全相關的哈希計算。

總之,MD5 早已不再安全,PHP 的弱類型比較 == 又讓這個問題雪上加霜。在實際開發中,我們應該避免使用 MD5 進行身份驗證。


user avatar u_17569005 頭像 invalidnull 頭像 pannideniupai 頭像 crmeb 頭像 old_it 頭像 openpie 頭像 youyudetusi 頭像 xiangchujiadepubu 頭像 chunzhendexiaogou 頭像 wilburxu 頭像 saxiaoyige 頭像
點贊 11 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.