- 從0到1:CTFer成長(zhǎng)之路
- Nu1L戰(zhàn)隊(duì)
- 5006字
- 2021-01-07 17:32:06
3.2 Python的安全問(wèn)題
因?yàn)镻ython實(shí)現(xiàn)各種功能非常簡(jiǎn)單、快速,所以應(yīng)用越來(lái)越普遍。同時(shí)由于Python的特性問(wèn)題如反序列化、SSTI等十分有趣,因此CTF比賽中也開(kāi)始對(duì)Python的特性問(wèn)題進(jìn)行利用的考察。本節(jié)將介紹CTF比賽的Python題目中常見(jiàn)的考點(diǎn),介紹相關(guān)漏洞的繞過(guò)方式;結(jié)合代碼或例題進(jìn)行分析,讓讀者在遇到Python代碼時(shí)快速找到相關(guān)漏洞點(diǎn),并進(jìn)行利用。由于Python 2與Python 3部分功能存在差異,實(shí)現(xiàn)可能有些區(qū)別。下面的內(nèi)容中,如果沒(méi)有其他特殊說(shuō)明,則Python 2和Python 3在相關(guān)漏洞的原理上并沒(méi)有區(qū)別。
3.2.1 沙箱逃逸
CTF的題目中存在一種讓用戶提交一段代碼給服務(wù)端、服務(wù)端去運(yùn)行的題型,出題者也會(huì)通過(guò)各種方式過(guò)濾各種高風(fēng)險(xiǎn)庫(kù)、關(guān)鍵詞等。對(duì)于這類(lèi)問(wèn)題,我們根據(jù)過(guò)濾程度由低到高,逐一介紹繞過(guò)的思路。
3.2.1.1 關(guān)鍵詞過(guò)濾
關(guān)鍵詞過(guò)濾是最簡(jiǎn)單的過(guò)濾方式,如過(guò)濾“l(fā)s”或“system”。Python是動(dòng)態(tài)語(yǔ)言,有著靈活的特性,這種情況非常容易繞過(guò)。例如:


對(duì)于字符串,我們還可以加入拼接、倒序或者base64編碼等。
3.2.1.2 花樣import
在Python中,想使用指定的模塊最常用的方法是顯式import,所以很多情況下import也會(huì)被過(guò)濾。不過(guò)import有多種方法,需要逐一嘗試。

另外,如果可以控制Python的代碼,在指定目錄中寫(xiě)入指定文件名的Python文件,也許可以達(dá)到覆蓋沙箱中要調(diào)用模塊的目的。比如,在當(dāng)前目錄中寫(xiě)入random.py,再在Python中import random時(shí),執(zhí)行的就是我們的代碼。例如:

這里利用的是Python導(dǎo)入模塊的順序問(wèn)題,Python搜索模塊的順序也可通過(guò)sys.path查看。如果可以控制這個(gè)變量,我們可以方便地覆蓋內(nèi)置模塊,通過(guò)修改該路徑,可以改變Python在import模塊時(shí)的查找順序,在搜索時(shí)優(yōu)先找到我們可控的路徑下的代碼,達(dá)成繞過(guò)沙箱的目的。例如:

除了sys.path,sys.modules是另一個(gè)與加載模塊有關(guān)的對(duì)象,包含了從Python開(kāi)始運(yùn)行起被導(dǎo)入的所有模塊。如果從中將部分模塊設(shè)置為None,就無(wú)法再次引入了。例如:

如果將模塊從sys.modules中剔除,就徹底不可用了。不過(guò)可以觀察到,其中的值都是路徑,所以可以手動(dòng)將路徑放回,然后就可以利用了。


同理,這個(gè)值被設(shè)置為可控模塊也可能造成任意代碼執(zhí)行。
如果可控的是ZIP文件,也可以使用zipimport.zipimporter實(shí)現(xiàn)上面的效果,不再贅述。
3.2.1.3 使用繼承等尋找對(duì)象
在Python中,一切都是對(duì)象,所以我們可以使用Python的內(nèi)置方法找到對(duì)象的父類(lèi)和子類(lèi),如[].__class__是<class'list'>,[].__class__.__mro__是(<class'list'>,<class'object'>),而[].__class__.__mro__[-1].__subclasses__()可以找到object的所有子類(lèi)。
比如,第40項(xiàng)是file對(duì)象(實(shí)際的索引可能不同,需要?jiǎng)討B(tài)識(shí)別),可以用于讀寫(xiě)文件。

Python中直接使用不需要import的函數(shù),如open、eval屬于全局的module__builtins__,所以可以嘗試__builtins__.open()等用法。若函數(shù)被刪除了,還可以使用reload()函數(shù)找回。

3.2.1.4 eval類(lèi)的代碼執(zhí)行
eval類(lèi)函數(shù)在任何語(yǔ)言中都是一個(gè)危險(xiǎn)的存在,我們可以在Python中嘗試,可以通過(guò)exec()(Python 2)、execfile()、eval()、compile()、input()(Python 2)等動(dòng)態(tài)執(zhí)行一段Python代碼。

3.2.2 格式化字符串
CTF的Python題目中會(huì)涉及Jinja2之類(lèi)的模板引擎的注入。這些漏洞常常由于服務(wù)器端沒(méi)有對(duì)用戶的輸入進(jìn)行過(guò)濾,就直接帶入了服務(wù)器端對(duì)相關(guān)頁(yè)面的渲染過(guò)程中。通過(guò)注入模板引擎的一些特定的指令格式,如{{1+1}}返回了2,我們可以得知漏洞存在于相關(guān)Web頁(yè)面中。類(lèi)似這種特性不僅限于Web應(yīng)用中,也存在于Python原生的字符串中。
3.2.2.1 最原始的%
如下代碼實(shí)現(xiàn)了登錄功能,由于沒(méi)有對(duì)用戶的輸入進(jìn)行過(guò)濾,直接帶入了print的輸出過(guò)程,從而導(dǎo)致了用戶密碼的泄露。

比如,用戶輸入“%(password)s”就可以獲取用戶的真實(shí)密碼。
3.2.2.2 format方法相關(guān)
上述的例子還可以使用format方法進(jìn)行改寫(xiě)(僅涉及關(guān)鍵部分):

此時(shí)若passwd="{password}",也可以實(shí)現(xiàn)3.2.2.1節(jié)中獲取用戶真實(shí)密碼的目的。除此之外,format方法還有其他用途。例如,以下代碼

會(huì)先把0替換為format中的參數(shù),再繼續(xù)獲取相關(guān)的屬性。由此我們可以獲取代碼中的敏感信息。
下面引用來(lái)自于http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/的例子:


如果format_string為{event.__init__.__globals__[CONFIG][SECRET_KEY]},就可以泄露敏感信息。
理論上,我們可以參考上文,通過(guò)類(lèi)的各種繼承關(guān)系找到想要的信息。
3.2.2.3 Python 3.6中的f字符串
Python 3.6中新引入了f-strings特性,通過(guò)f標(biāo)記,讓字符串有了獲取當(dāng)前context中變量的能力。例如:

不僅限制為屬性,代碼也可以執(zhí)行了。例如:

但是目前沒(méi)有把普通字符串轉(zhuǎn)換為f字符串的方法,也就是說(shuō),用戶可能無(wú)法控制一個(gè)f字符串,可能無(wú)法利用。
3.2.3 Python模板注入
Python的很多Web應(yīng)用涉及模板的使用,如Tornado、Flask、Django。有時(shí)服務(wù)器端需要向用戶端發(fā)送一些動(dòng)態(tài)的數(shù)據(jù)。與直接用字符串拼接的方式不同,模板引擎通過(guò)對(duì)模板進(jìn)行動(dòng)態(tài)的解析,將傳入模板引擎的變量進(jìn)行替換,最終展示給用戶。
SSTI服務(wù)端模板注入正是因?yàn)榇a中通過(guò)不安全的字符串拼接的方式來(lái)構(gòu)造模板文件而且過(guò)分信任了用戶的輸入而造成的。大多數(shù)模板引擎自身并沒(méi)有什么問(wèn)題,所以在審計(jì)時(shí)我們的重點(diǎn)是找到一個(gè)模板,這個(gè)模板通過(guò)字符串拼接而構(gòu)造,而且用戶輸入的數(shù)據(jù)會(huì)影響字符串拼接過(guò)程。
下面以Flask為例(與Tornado的模板語(yǔ)法類(lèi)似,這里只關(guān)注如何發(fā)現(xiàn)關(guān)鍵的漏洞點(diǎn))。在處理懷疑含有模板注入的漏洞的網(wǎng)站時(shí),先關(guān)注render_*這類(lèi)函數(shù),觀察其參數(shù)是否為用戶可控。如果存在模板文件名可控的情況,如

配合上傳漏洞,構(gòu)造模板,則完成模板注入。
對(duì)于下面的例子,我們應(yīng)先關(guān)注render_template_string(template)函數(shù),其參數(shù)template通過(guò)格式化字符串的方式構(gòu)造,其中request.url沒(méi)有任何過(guò)濾,可以直接由用戶控制。

那么直接在URL中傳入惡意代碼,如“{{self}}”,拼接至template中。由于模板在渲染時(shí)服務(wù)器會(huì)自動(dòng)尋找服務(wù)器渲染時(shí)上下文的有關(guān)內(nèi)容,因此將其填充到模板中,就導(dǎo)致了敏感信息的泄露,甚至執(zhí)行任意代碼的問(wèn)題。
通過(guò)在本地搭建與服務(wù)器相同的環(huán)境,查看渲染時(shí)上下文的信息,這時(shí)最簡(jiǎn)單的利用是用{{variable}}將上下文的變量導(dǎo)出,更好的利用方式是找到可以直接利用的庫(kù)或函數(shù),或者通過(guò)上文提到的繼承等尋找對(duì)象的手段,從而完成任意代碼的執(zhí)行。
3.2.4 urllib和SSRF
Python的urllib庫(kù)(Python 2中為urllib2,Python 3中為urllib)有一些HTTP下的協(xié)議流注入漏洞。如果攻擊者可以控制Python代碼訪問(wèn)任意URL,或者讓Python代碼訪問(wèn)一個(gè)惡意的Web Server,那么這個(gè)漏洞可能危害內(nèi)網(wǎng)服務(wù)安全。
對(duì)于這類(lèi)漏洞,我們主要關(guān)注服務(wù)器采用的Python版本是否存在相應(yīng)的漏洞,以及攻擊的目標(biāo)是否會(huì)受到SSRF攻擊的影響,如利用某個(gè)圖片下載的Python服務(wù)去攻擊內(nèi)網(wǎng)部署的一臺(tái)未加密的Redis服務(wù)器。
3.2.4.1 CVE-2016-5699
CVE-2016-5699:Python 2.7.10以前的版本和Python 3.4.4以前的3.x版本中的urllib2和urllib中的HTTPConnection.putheader函數(shù)存在CRLF注入漏洞。遠(yuǎn)程攻擊者可借助URL中的CRLF序列,利用該漏洞注入任意HTTP頭。
在HTTP解析host的時(shí)候可以接收urlencode編碼的值,然后host的值會(huì)在解碼后包含在HTTP數(shù)據(jù)流中。這個(gè)過(guò)程中,由于沒(méi)有進(jìn)一步的驗(yàn)證或者編碼,就可以注入一個(gè)換行符。
例如,在存在漏洞的Python版本中運(yùn)行以下代碼:

其功能是從命令行參數(shù)接收一個(gè)URL,然后訪問(wèn)它。為了查看urllib請(qǐng)求時(shí)發(fā)送的HTTP頭,我們用nc命令來(lái)監(jiān)聽(tīng)端口,查看該端口收到的數(shù)據(jù)。

此時(shí)向127.0.0.1:12345發(fā)送一個(gè)正常的請(qǐng)求,可以看到HTTP頭為:

然后我們使用惡意構(gòu)造的地址

可以看到HTTP頭變成了:

對(duì)比之前正常的請(qǐng)求方式,X-injected:header行是新增的,這樣就造成了我們可以使用類(lèi)似SSRF攻擊手法的方式,攻擊內(nèi)網(wǎng)的Redis或其他應(yīng)用。
除了針對(duì)IP,這個(gè)攻擊漏洞在使用域名的時(shí)候也可以進(jìn)行,但是要插入一個(gè)空字節(jié)才能進(jìn)行DNS查詢。比如,URL:http://localhost%0d%0ax-bar:%20:12345/foo進(jìn)行解析會(huì)失敗的,但是URL:http://localhost%00%0d%0ax-bar:%20:12345/foo可以正常解析并訪問(wèn)127.0.0.1。
注意,HTTP重定向也可以利用這個(gè)漏洞,如果攻擊者提供的URL是惡意的Web Server,那么服務(wù)器可以重定向到其他URL,也可以導(dǎo)致協(xié)議注入。
3.2.4.2 CVE-2019-9740
CVE-2019-9740:Python urllib同樣存在CRLF注入漏洞,攻擊者可通過(guò)控制URL參數(shù)進(jìn)行CRLF注入攻擊。例如,我們修改上面CVE-2016-5699的poc,就可以復(fù)現(xiàn)了

可以看到,HTTP頭如下:

3.2.5 Python反序列化
反序列化在每種語(yǔ)言中都有相應(yīng)的實(shí)現(xiàn)方式,Python也不例外。在反序列化的過(guò)程中,由于反序列化庫(kù)的實(shí)現(xiàn)不同,在太相信用戶輸入的情況下,將用戶輸入的數(shù)據(jù)直接傳入反序列化庫(kù)中,就可能導(dǎo)致任意代碼執(zhí)行的問(wèn)題。Python中可能存在問(wèn)題的庫(kù)有pickle、cPickle、PyYAML,其中應(yīng)該重點(diǎn)關(guān)注的方法如下:pickle.load(),pickle.loads(),cPickle.load(),cPickle.loads(),yaml.load()。下面重點(diǎn)討論pickle的用法,其他反序列化方法類(lèi)似。
pickle中存在__reduce__魔術(shù)方法,來(lái)決定類(lèi)如何進(jìn)行反序列化。__reduce__方法返回值為長(zhǎng)度一個(gè)2~5的元組時(shí),將使用該元組的內(nèi)容將該類(lèi)的對(duì)象進(jìn)行序列化,其中前兩項(xiàng)為必填項(xiàng)。元組的內(nèi)容的第一項(xiàng)為一個(gè)callable的對(duì)象,第二項(xiàng)為調(diào)用callable對(duì)象時(shí)的參數(shù)。比如通過(guò)如下exp,將生成在反序列化時(shí)執(zhí)行os.system("id")的payload。在用戶對(duì)需要進(jìn)行反序列化的字符串有控制權(quán)時(shí),將payload傳入,就會(huì)導(dǎo)致一些問(wèn)題。例如,將以下反序列化產(chǎn)生的結(jié)果直接傳入pickle.loads(),則會(huì)執(zhí)行os.system("id")。


pickle中存在很多opcode,通過(guò)這些opcode,構(gòu)造調(diào)用棧,我們可以實(shí)現(xiàn)很多其他功能。比如,code-breaking 2018中涉及一道反序列化的題目,在反序列化階段限制了可供反序列化的庫(kù),__reduce__只能實(shí)現(xiàn)對(duì)一個(gè)函數(shù)的調(diào)用,于是需要手工編寫(xiě)反序列化的內(nèi)容,以完成對(duì)過(guò)濾的繞過(guò)及任意代碼執(zhí)行的目的。
3.2.6 Python XXE
無(wú)論什么語(yǔ)言,在涉及對(duì)XML的處理時(shí)都有可能出現(xiàn)XXE相關(guān)漏洞,于是在審計(jì)一段代碼中是否存在XXE漏洞時(shí),最主要的是找對(duì)XML的處理過(guò)程,關(guān)注其中是否禁用了對(duì)外部實(shí)體的處理。比如,對(duì)于某個(gè)Web程序,通過(guò)請(qǐng)求頭中的Content-type判斷用戶輸入的類(lèi)型,為JSON時(shí)調(diào)用JSON的處理方法,為XML時(shí)調(diào)用XML的處理方法,而這個(gè)過(guò)程中剛好沒(méi)有對(duì)外部實(shí)體進(jìn)行過(guò)濾,這就導(dǎo)致了在用戶輸入XML時(shí)的XXE問(wèn)題。
XXE就是XML Entity(實(shí)體)注入。Entity(實(shí)體)的作用類(lèi)似Word中的“宏”,用戶可以預(yù)定義一個(gè)Entity,再在一個(gè)文檔中多次調(diào)用,或在多個(gè)文檔中調(diào)用同一個(gè)Entity。XML定義了兩種Entity:普通Entity,在XML文檔中使用;參數(shù)Entity,在DTD文件中使用。
在Python中處理XML最常用的就是xml庫(kù),我們需要關(guān)注其中的parse方法,查看輸入的XML是否直接處理用戶的輸入,是否禁用了外部實(shí)體,即審計(jì)時(shí)的重點(diǎn)。但是,Python從3.7.1版開(kāi)始,默認(rèn)禁止了XML外部實(shí)體的解析,所以在審計(jì)時(shí)也要注意版本。具體xml庫(kù)存在的安全問(wèn)題,讀者可以查閱xml庫(kù)的官方文檔:https://docs.python.org/3/library/xml.html。
下述代碼中包含兩段XXE常見(jiàn)的payload,分別用于讀取文件和探測(cè)內(nèi)網(wǎng),再通過(guò)Python對(duì)其中的XML進(jìn)行解析。代碼本身沒(méi)有對(duì)外部實(shí)體進(jìn)行限制,從而導(dǎo)致了XXE漏洞。


運(yùn)行這段代碼,就可以打印出/etc/passwd的內(nèi)容,而且127.0.0.1:8005可以收到一個(gè)HTTP請(qǐng)求。

除了這種情況,有時(shí)源程序在解析完XML數(shù)據(jù)后,并不會(huì)將其中的內(nèi)容進(jìn)行輸出,此時(shí)無(wú)法從返回結(jié)果中獲取我們需要的內(nèi)容。在這種情況下,我們可以利用Blind XXE作為攻擊方式,同樣是利用對(duì)XML實(shí)體的各種操作,攻擊載荷如下所示。

先用file://或php://filter獲取目標(biāo)文件的內(nèi)容,然后將內(nèi)容以http請(qǐng)求發(fā)送到接收數(shù)據(jù)的服務(wù)器。由于不能在實(shí)體定義中引用參數(shù)實(shí)體,因此我們需要將嵌套的實(shí)體聲明放到一個(gè)外部dtd文件中,如下文的eval.dtd。

在服務(wù)器上建立監(jiān)聽(tīng)即可實(shí)現(xiàn)數(shù)據(jù)的外帶。同時(shí)在某些情況下,需要外帶的數(shù)據(jù)中可能存在特殊字符,此時(shí)需要通過(guò)CDATA將數(shù)據(jù)進(jìn)行包裹,最終實(shí)現(xiàn)外帶。由于在互聯(lián)網(wǎng)上有很多相關(guān)資料,故不在此處做更多介紹。
3.2.7 sys.audit
2018年6月,Python的PEP-0578新增了一個(gè)審計(jì)框架,可以提供給測(cè)試框架、日志框架和安全工具,來(lái)監(jiān)控和限制Python Runtime的行為。
Python提供了對(duì)許多常見(jiàn)操作系統(tǒng)的各種底層功能的訪問(wèn)方式。雖然這對(duì)于“一次編寫(xiě),隨處運(yùn)行”腳本非常有用,但使監(jiān)控用Python編寫(xiě)的軟件變得困難。由于Python本機(jī)原生系統(tǒng)API,因此現(xiàn)有的監(jiān)控審計(jì)工具要么上下文信息是受限的,要么會(huì)直接被繞過(guò)。
上下文受限是指,系統(tǒng)監(jiān)視可以報(bào)告發(fā)生了某個(gè)操作,但無(wú)法解釋導(dǎo)致該操作的事件序列。例如,系統(tǒng)級(jí)別的網(wǎng)絡(luò)監(jiān)視可以報(bào)告“開(kāi)始偵聽(tīng)在端口5678”,但可能無(wú)法在程序中提供進(jìn)程ID、命令行參數(shù)、父進(jìn)程等信息。
審計(jì)繞過(guò)是指,一個(gè)功能可以使用多種方式完成,監(jiān)控了一部分,使用其他的就可以繞過(guò)。例如,在審計(jì)系統(tǒng)中專門(mén)監(jiān)視調(diào)用curl發(fā)出HTTP請(qǐng)求,但Python的urlretrieve函數(shù)沒(méi)有被監(jiān)控。
另外,對(duì)于Python有點(diǎn)獨(dú)特的是,通過(guò)操縱導(dǎo)入系統(tǒng)的搜索路徑或在路徑上放置文件而不是預(yù)期的文件,很容易影響應(yīng)用程序中運(yùn)行的代碼。當(dāng)開(kāi)發(fā)人員創(chuàng)建與他們打算使用的模塊同名的腳本時(shí),通常會(huì)出現(xiàn)這種情況。例如,一個(gè)random.py文件嘗試導(dǎo)入標(biāo)準(zhǔn)庫(kù)random,實(shí)際上執(zhí)行的是用戶的random.py。
3.2.8 CTF Python案例
3.2.8.1 皇家線上賭場(chǎng)(SWPU 2018)
題目是一個(gè)Flask Web,通過(guò)任意文件讀取獲取views.py的代碼:


__init__.py文件內(nèi)容如下:

然后使用得到的secret_key,我們可以偽造Session,生成一個(gè)符合getflag條件的Session。
getflag的format可以直接注入一些數(shù)據(jù),但是需要跳出g.u,題目中給了提示:為了方便,給user寫(xiě)了save方法,所以直接使用__globals__跳出得到flag,payload見(jiàn)圖3-2-1。

圖3-2-1
3.2.8.2 mmmmy(網(wǎng)鼎杯2018線上賽)
偽造JWT登入后是一個(gè)留言功能,發(fā)現(xiàn)輸入的東西都會(huì)原原本本地打印在頁(yè)面上,于是猜測(cè)這是一個(gè)SSTI。測(cè)試后發(fā)現(xiàn)過(guò)濾了很多東西,如“'”“"”“os”“_”“{{”等,只要出現(xiàn)了這些關(guān)鍵字,就直接打印None。雖然過(guò)濾了“{{”,但是可以使用“{%”,如“{%if 1%}1{%endif%}”會(huì)打印“1”。
我們思考需要繞過(guò)的地方。首先“__”被過(guò)濾,可以使用“[]”結(jié)合request來(lái)繞過(guò),如“{%if()[request.args.a]%}”,URL中的“/bbs?a=__class__”。然后可以構(gòu)造一個(gè)讀取文件的payload:

但是報(bào)了500錯(cuò)誤,考慮是沒(méi)有chr函數(shù)。那么如法炮制,獲取chr函數(shù):

然后可以使用腳本進(jìn)行盲注,見(jiàn)圖3-2-2。

圖3-2-2

圖3-2-2(續(xù))
除了盲注,還有一種方法可以直接打印明文,即使用jinja2中的print,見(jiàn)圖3-2-3。打印結(jié)果見(jiàn)圖3-2-4。

圖3-2-3

圖3-2-4
- 科技安全:戰(zhàn)略實(shí)踐與展望
- Learning Python for Forensics
- 暗戰(zhàn)亮劍:黑客滲透與防御全程實(shí)錄
- Preventing Digital Extortion
- .NET安全攻防指南(上冊(cè))
- 網(wǎng)絡(luò)安全三十六計(jì):人人該懂的防黑客技巧
- Computer Forensics with FTK
- Spring Security(Third Edition)
- 網(wǎng)絡(luò)用戶行為的安全可信分析與控制
- 網(wǎng)絡(luò)關(guān)鍵設(shè)備安全檢測(cè)實(shí)施指南
- 持續(xù)集成:軟件質(zhì)量改進(jìn)和風(fēng)險(xiǎn)降低之道
- 網(wǎng)絡(luò)空間安全導(dǎo)論
- 計(jì)算機(jī)網(wǎng)絡(luò)安全實(shí)驗(yàn)指導(dǎo)
- 信息內(nèi)容安全管理及應(yīng)用
- Web安全攻防從入門(mén)到精通