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