關于Python漏洞挖掘那些不得不提的事兒
前言
Python因其在開發更大、更復雜應用程序方面獨特的便捷性,使得它在計算機環境中變得越來越不可或缺。雖然其明顯的語言清晰度和使用友好度使得軟件工程師和系統管理員放下了戒備,但是他們的編碼錯誤還是有可能會帶來嚴重的安全隱患。
這篇文章的主要受眾是還不太熟悉Python的人,其中會提及少量與安全有關的行為以及有經驗開發人員遵循的規則。
輸入函數
在Python2強大的內置函數中,輸入函數完全就是一個大的安全隱患。一旦調用輸入函數,任何從stdin中讀取的數據都會被認定為Python代碼:
$ python2
>>> input()
dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> input()
__import__('sys').exit()
$
顯然,只要腳本stdin中的數據不是完全可信的,輸入函數就是有危險的。Python 2 文件將 raw_input 認定為一個安全的選擇。在Python3中,輸入函數相當于是 raw_input,這樣就可以完全修復這一問題。
assert語句
還有一條使用 assert 語句編寫的代碼語句,作用是捕捉 Python 應用程序中下一個不可能條件。
def verify_credentials(username, password):
assert username and password, 'Credentials not supplied by caller'
... authenticate possibly null user with null password ...
然而,Python在編譯源代碼到優化的字節代碼 (如 python-O) 時不會有任何的assert 語句說明。這樣的移除使得程序員編寫用來抵御攻擊的代碼保護都形同虛設。
這一弱點的根源就是assert機制只是用于測試,就像是c++語言中那樣。程序員必須使用其他手段才能確保數據的一致性。
可重用整數
在Python中一切都是對象,每一個對象都有一個可以通過 id 函數讀取的唯一標示符。可以使用運算符弄清楚是否有兩個變量或屬性都指向相同的對象。整數也是對象,所以這一操作實際上是一種定義:
>>> 999+1 is 1000
False
上述操作的結果可能會令人大吃一驚,但是要提醒大家的是這樣的操作是同時使用兩個對象標示符,這一過程中并不會比較它們的數值或是其它任何值。但是:
>>> 1+1 is 2
True
對于這種行為的解釋就是Python當中有一個對象集合,代表了最開始的幾百個整數,并且會重利用這些整數以節省內存和對象創建。更加令人疑惑的就是,不同的Python版本對于“小整數”的定義是不一樣的。
這里所指的緩存永遠不會使用運算符進行數值比較,運算符也專門是為了處理對象標示符。
浮點數比較
處理浮點數可能是一件更加復雜的工作,因為十進制和二進制在表示分數的時候會存在有限精度的問題。導致混淆的一個常見原因就是浮點數對比有時候可能會產生意外的結果。下面是一個著名的例子:
>>> 2.2 * 3.0 == 3.3 * 2.0
False
這種現象的原因是一個舍入錯誤:
>>> (2.2 * 3.0).hex()
'0x1.a666666666667p+2'
>>> (3.3 * 2.0).hex()
'0x1.a666666666666p+2'
另一個有趣的發現就是Python float 類型支持無限概念。一個可能的原因就是任何數都要小于無限:
>>> 10**1000000 > float('infinity')
False
但是在Python3中,有一種類型的對象不支持無限:
>>> float > float('infinity')
True
一個最好的解決辦法就是堅持使用整數算法,還有一個辦法就是使用十進制內核模塊,這樣可以為用戶屏蔽煩人的細節問題和缺陷。
一般來說,只要有任何算術運算就必須要小心舍入錯誤。詳情可以參閱 Python 文檔中的《發布和局限性》一章。