PHP中“==”運算子的安全問題

安全客發表於2016-08-10

前言

PHP是一種通用的開源指令碼語言,它的語法混合了C,Java,以及Perl等優秀語言的語法。除此之外,它還提供了大量的函式庫可供開發人員使用。但是,如果使用不當,PHP也會給應用程式帶來非常大的安全風險。

在這篇文章中,我們將會對PHP應用程式中經常會出現的一些問題進行深入地分析,尤其是當我們使用“==”(比較運算子)來進行字串比較時,可能會出現的一些安全問題。雖然近期有很多文章都圍繞著這一話題進行過一些探討,但我決定從“黑盒測試”的角度出發,討論一下如何利用這個問題來對目標進行滲透和攻擊。首先,我會對引起這個問題的根本原因進行分析,以便我們能夠更加深入地理解其工作機制,這樣才可以保證我們能夠儘可能地避免這種安全問題的發生。

問題的描述

在2011年,PHP官方漏洞追蹤系統發現,當字串與數字在進行比較的時候,程式會出現某些非常奇怪的現象。從安全的角度出發,這個問題實際上並不能算是一個安全問題。比如說,你可以看到下面這段程式碼:

http://p6.qhimg.com/t01f30de1bd37f60122.png

實際上,當使用類似“==”這樣的比較運算子進行操作時,就會出現這樣的情況。上面這個例子中出現的問題不能算是一個漏洞,因為它是PHP所提供的一種名為“型別轉換”的功能。從本質上來分析,當我們使用特定的比較運算子(例如== , !=, <>)來進行操作時,PHP首先會嘗試去確定參與比較的資料型別。但是這樣的一種型別轉換機制將有可能導致計算結果與我們預期的結果有較大出入,而且也會帶來非常嚴重的安全問題。安全研究專家在該問題的完整披露報告中寫到:這種型別轉化機制將有可能導致許可權提升,甚至還會使程式的密碼驗證過程變得不安全。

Gynvael寫過一篇關於這一話題的經典文章,PHP等號運算子“==”所涵蓋的資料型別非常廣泛,我們給大家提供了一個較為完整的比較參考列表,並給出了一些示例,具體內容如下所示:

http://p1.qhimg.com/t013f775cc8ebcae16c.png

正如你所看到的,當我們使用“==”來比較這些數字字串時,參與比較的就是字串中數字的實際大小,從安全的角度出發,這就是一個非常有趣的問題了。在這種情況下,你可以使用科學計數法來表示一個數字,並將其放在一個字串中,PHP將會自動把它作為一個數字型別來處理。我們之所以會得到這樣的輸出型別,是因為PHP使用了一種雜湊演算法(通常使用十六進位制數值表示)來進行處理。比如說,如果一個數字為0,那麼在進行鬆散比較的過程中,PHP會自動對其型別進行轉換,但其值永遠為0。對於一個給定的雜湊演算法而言,密碼就有可能會變成可以被替換的了。比如說,當密碼的雜湊值被轉換成使用科學計數法來表示的數字時,將有可能正好與其他的密碼雜湊相匹配。這樣一來,即使是一個完全不同的密碼,也有可能可以通過系統的驗證。但有趣的是,當某些採用科學計數法表示的數字在進行比較的時候,結果可能會讓你意想不到:

http://p9.qhimg.com/t01aeb9ec2d82dd2c58.png

從“黑盒測試”的角度出發來考慮這個問題

從靜態分析的角度來看,這些安全問題就顯得有些普通了。但如果我們從黑盒的角度來看待這些問題,我們能夠得到什麼樣的啟發呢?對於應用程式中的任何使用者賬號而言,如果應用程式使用了當前最為流行的雜湊雜湊演算法(例如SHA1和MD5)來對密碼進行處理,而你在對密碼雜湊進行驗證的時候使用了PHP的鬆散比較,那麼此時就有可能出現安全問題。我們現在可以考慮進行一次典型的滲透測試,你可以建立一個普通的賬號,將密碼設定成雜湊值類似的其中一個密碼,然後使用其他的密碼進行登入操作。很明顯,系統的安全性完全取決於你所使用的雜湊演算法。所以,我們假設你沒有在雜湊演算法中使用“Salt”值,那麼你至少得使用兩種不同的雜湊演算法來對密碼進行處理。

現在,在我們去對這些密碼組合進行研究之前,我們還應該考慮到一點——即密碼的要求。因為我們在對這些密碼和雜湊演算法進行分析之前,首先得確保我們所設定的初始密碼複合了密碼複雜度的要求,否則我們的分析和研究將會沒有任何的意義。因此,我們得確保我們的密碼長度至少為八個字元,密碼中包含有大小寫字母,數字,以及至少一個特殊字元:具體如下所示:

import random
import hashlib
import re
import string
import sys
prof = re.compile("^0+ed*$") # you can also consider: re.compile("^d*e0+$")
prefix = string.lower(sys.argv[1])+'!'+string.upper(sys.argv[1])+"%s"
num=0
while True:
    num+=1
    b = hashlib.sha256(prefix % num).hexdigest()
    if (b[0]=='0' and prof.match(b)):
        print(prefix+str(num),b)

為此,我專門編寫了一個Python指令碼,雖然我沒有竭盡全力去優化這個指令碼的效能,但是在PyPy編譯器的幫助下,這個精心編寫的指令碼可以在我的AMD FX8350所有可用的CPU核心中穩定執行。除此之外,我還使用到了hashlib庫中的雜湊函式,而且為了避免遇到Python GIL的程式同步問題,我還生成了獨立的程式來對密碼資料進行處理。不僅如此,我還使用了非常複雜的技術來為每一個密碼生成不同的字首,正如上面這段程式碼所示。

分析結果

在經過了一個多小時的分析之後,我得到了四個密碼的SHA1值。令我感到驚訝的是,得到四個密碼的MD5值所需的時間竟然更短。

密碼的計算結果十分相似,具體如下所示:

https://i.iter01.com/images/79e91c929626f73e53300b5d9f41b9c9a1ba4ae643e12ab7e288c553d93a2f5c.png

你可以隨意選取兩個密碼來進行對比,對比的演示結果如下:

http://p8.qhimg.com/t018aa1f59b2bce56d9.png

如果你無法得到如上圖所示的計算結果,那麼你應該感到幸運。你可以嘗試將使用者名稱和密碼捆綁在一起,然後使用帶“salt”值的雜湊演算法來進行計算。你只需要修改一小部分程式碼即可實現,點選“這裡”獲取修改後的指令碼。

解決方案

PHP給我們提供了一個解決方案,如果你想要對比雜湊值,你應該使用password_verify()或hash_equals()這兩個函式。它們會對資料進行嚴格比較,並排除一些其他的干擾因素。但是請你注意,hash_equals()函式也可以用於字串的比較。

分析結論

雖然我們的分析步驟執行起來有些過於複雜,但是從黑盒測試的角度出發,我們所描述的方法也許可以給大家提供一些有價值的資訊。如果某個應用程式中的密碼採用了這樣的一種驗證機制,那麼它所帶來的安全問題將會超出PHP資料型別轉換本身所存在的問題。

問題遠不止於此

這個問題給我們帶來的影響遠遠不止於此。攻擊者可以將這些密碼新增到字典檔案中,然後對應用程式中的所有使用者進行暴力破解攻擊。而且,如果應用程式的密碼恢復機制中存在不安全的因素,攻擊者還有可能對目標賬號進行不限次數的攻擊,直到攻擊成功為止。

原始碼

點選“這裡”獲取Python原始碼。

相關文章