- 從0到1:CTFer成長之路
- Nu1L戰(zhàn)隊
- 6505字
- 2021-01-07 17:32:00
2.4 Web文件上傳漏洞
文件上傳在Web業(yè)務(wù)中很常見,如用戶上傳頭像、編寫文章上傳圖片等。在實現(xiàn)文件上傳時,如果后端沒有對用戶上傳的文件做好處理,會導(dǎo)致非常嚴(yán)重的安全問題,如服務(wù)器被上傳惡意木馬或者垃圾文件。因其分類眾多,本節(jié)主要介紹PHP常見的一些上傳問題。
2.4.1 基礎(chǔ)文件上傳漏洞
圖2-4-1是一段基礎(chǔ)的PHP上傳代碼,卻存在文件上傳漏洞。PHP的文件上傳通常使用move_uploaded_file方法配合$_FILES變量實現(xiàn),圖中的代碼直接使用了用戶上傳文件的文件名作為后端保存的文件名,會導(dǎo)致任意文件上傳漏洞。所以在該上傳點可以上傳惡意PHP腳本文件(見圖2-4-2)。

圖2-4-1

圖2-4-2
2.4.2 截斷繞過上傳限制
2.4.2.1 00截斷
00截斷是繞過上傳限制的一種常見方法。在C語言中,“\0”是字符串的結(jié)束符,如果用戶能夠傳入“\0”,就能夠?qū)崿F(xiàn)截斷。
00截斷繞過上傳限制適用的場景為,后端先獲取用戶上傳文件的文件名,如x.php\00.jpg,再根據(jù)文件名獲得文件的實際后綴jpg;通過后綴的白名單校驗后,最終在保存文件時發(fā)生截斷,實現(xiàn)上傳的文件為x.php。
PHP的底層代碼為C語言,自然存在這種問題,但是實際PHP使用$_FILES實現(xiàn)文件上傳時并不存在00截斷繞過上傳限制問題,因為PHP在注冊$_FILES全局變量時已經(jīng)產(chǎn)生了截斷。上傳文件名為x.php\00.jpg的文件,而注冊到$_FILES['name']的變量值為x.php,根據(jù)該值得到的后綴為php,因此無法通過后綴的白名單校驗,測試截圖見圖2-4-3(文件名中包含不可見字符“\0”)。

圖2-4-3
PHP處理上傳請求的部分調(diào)用棧如下:

在rfc1867_post_handle方法中調(diào)用multipart_buffer_headers方法,通過對mbuff上傳包進(jìn)行處理,得到header結(jié)構(gòu)體:


在multipart_buffer_headers方法中存在如下代碼:

從boundary中逐行讀出數(shù)據(jù),使用“:”分割出key和value;當(dāng)處理filename時,key值為Content-Disposition,value值為form-data;name="file";filename="a.php\0.jpg";然后執(zhí)行smart_string_appends宏定義的最終實現(xiàn)為memcpy,當(dāng)value復(fù)制到&buf_value時,“\0”造成了截斷。在截斷后,將buf_value.c添加到entry中,再通過zend_llist_add_element將entry添加到header結(jié)構(gòu)體中。


用于注冊$_FILES['name']的filename變量從header結(jié)構(gòu)體中獲得,所以最終注冊到$_FILES['name']的文件名為產(chǎn)生截斷后的文件名。
在Java中,jdk7u40以下版本存在00截斷問題,7u40后的版本,在上傳、寫入文件等操作中都會調(diào)用File的isInvalid()方法判斷文件名是否合法,即不允許文件名中含有“\0”,如果文件名不合法,將拋出異常退出流程。


2.4.2.2 轉(zhuǎn)換字符集造成的截斷
雖然PHP的$_FILES文件上傳不存在00截斷繞過上傳限制的問題,不過在文件名進(jìn)行字符集轉(zhuǎn)換的場景下也可能出現(xiàn)截斷繞過。PHP在實現(xiàn)字符集轉(zhuǎn)換時通常使用iconv()函數(shù),UTF-8在單字節(jié)時允許的字符范圍為0x00~0x7F,如果轉(zhuǎn)換的字符不在該范圍內(nèi),則會造成PHP_ICONV_ERR_ILLEGAL_SEQ異常,低版本PHP在PHP_ICONV_ERR_ILLEGAL_SEQ異常后不再處理后面字符造成截斷問題,見圖2-4-4。可以看出,當(dāng)PHP版本低于5.4時,轉(zhuǎn)換字符集能夠造成截斷,但5.4及以上版本會返回false。

圖2-4-4
若PHP版本低于5.4,只要out_buffer不為空,無論err為何值都能正常返回,見圖2-4-5。

圖2-4-5
而當(dāng)PHP版本為5.4及以上時,只有err為PHP_ICONV_ERR_SUCCESS即成功轉(zhuǎn)換且out_buffer不為空時,才會正常返回,否則返回FALSE,見圖2-4-6。
轉(zhuǎn)換字符集造成的截斷在繞過上傳限制中適用的場景為,先在后端獲取上傳的文件后綴,經(jīng)過后綴白名單判斷后,如果有對文件名進(jìn)行字符集轉(zhuǎn)換操作,那么可能出現(xiàn)安全問題。例如,在圖2-4-7中可以上傳x.php\x99.jpg文件,最終保存的文件名為x.php(見圖2-4-8)。實際案例可以參見http://www.yulegeyu.com/2019/06/18/Metinfo6-Arbitrary-File-Upload-Via-Iconv-Truncate。

圖2-4-6

圖2-4-7

圖2-4-8
2.4.3 文件后綴黑名單校驗繞過
黑名單校驗上傳文件后綴,即通過創(chuàng)建一個后綴名的黑名單列表,在上傳時判斷文件后綴名是否在黑名單列表中,在黑名單中則不進(jìn)行任何操作,不在則可以上傳,從而實現(xiàn)對上傳文件的過濾。
2.4.3.1 上傳文件重命名
測試代碼見圖2-4-9,在文件名重命名的場景下,可控的只有文件后綴,通常使用一些比較偏門的可解析的文件后綴繞過黑名單限制。
PHP常見的可執(zhí)行后綴為php3、php5、phtml、pht等,ASP常見的可執(zhí)行后綴為cdx、cer、asa等,JSP可以嘗試jspx等。見圖2-4-10,在上傳PHP文件被限制時,可以通過上傳PHTML文件實現(xiàn)繞過,見圖2-4-11和圖2-4-12。
可解析后綴在不同環(huán)境下不盡相同,需要多嘗試一些后綴。如果環(huán)境為Windows系統(tǒng),那么可以嘗試"php"、"php::$DATA"、"php."等后綴;或先上傳"a.php:.jpg",生成空a.php文件,再上傳"a.ph<"寫入文件內(nèi)容。在Windows環(huán)境下,文件名不區(qū)分大小寫,而in_array區(qū)分大小寫,所以可以嘗試大小寫后綴名繞過黑名單。若Web服務(wù)器配置了SSI,還可以嘗試上傳SHTML、SHT等文件命令執(zhí)行。

圖2-4-9

圖2-4-10

圖2-4-11

圖2-4-12
2.4.3.2 上傳文件不重命名
在上傳文件不重命名的場景下,除了尋找一些比較偏門的可解析的文件后綴,還可以通過上傳.htaccess或.user.ini配置文件實現(xiàn)繞過。
1.上傳.htaccess文件繞過黑名單
.htaccess是Apache分布式配置文件的默認(rèn)名稱,也可以在Apache主配置文件中通過AccessFileName指令修改分布式配置文件的名稱。Apache主配置文件中通過AllowOverride指令配置.htaccess文件中可以覆蓋主配置文件的那些指令,在低于2.3.8的版本中,AllowOverride指令默認(rèn)為All,在2.3.9及更高版本中默認(rèn)為None,即在高版本Apache中,默認(rèn)情況下.htaccess已無任何作用。不過即使AllowOverride為All,為了避免安全問題,也不能覆蓋所有主配置文件中的指令,具體可覆蓋指令可查看:http://httpd.apache.org/docs/2.2/mod/directive-dict.html#Context。在低于2.3.8版本時,因為默認(rèn)AllowOverride為all,可以嘗試上傳.htaccess文件修改部分配置,使用SetHandler指令使php解析指定文件,見圖2-4-13。
先上傳.htaccess文件,配置Files使PHP解析yu.txt文件,見圖2-4-14。
再上傳yu.txt文件到當(dāng)前目錄下,此時yu.txt已被當(dāng)做PHP文件解析。
除了上文中的SetHandler application/x-httpd-php,其實利用方法還有下面這種寫法:

具體的利用方式與上文相同,在此不再贅述。

圖2-4-13

圖2-4-14
2.上傳.user.ini文件繞過黑名單
自PHP 5.3.0起支持基于每個目錄的.htaccess風(fēng)格的INI文件,此類文件僅被CGI/FastCGI SAPI處理,其默認(rèn)文件名為.user.ini。當(dāng)然,也可以在主配置文件中使用user_ini.filename指令修改該配置文件名。
PHP文件被執(zhí)行時,除了加載主php.ini,還會在每個目錄下掃描INI文件,從被執(zhí)行的PHP文件所在目錄開始,一直上升到Web根目錄。
同樣,為了保證安全性,在.user.ini文件中也不能覆蓋所有php.ini中的配置。PHP中的每個配置都有其所屬的模式,模式指定了該配置能在哪些地方被修改,見圖2-4-15。

圖2-4-15
從官方手冊可知,配置存在4個模式,且PHP_INI_PREDIR模式只能在php.ini、.htaccess、httpd.conf中進(jìn)行配置,但是在實際中,PHP_INI_PREDIR模式的配置也可以在.user.ini文件中進(jìn)行配置,還存在一種php.ini only模式。disable_functions就是php.ini only模式,詳細(xì)配置模式可以從官方手冊中查看:https://www.php.net/manual/zh/ini.list.php。
在PHP_INI_PERDIR模式中存在兩個特殊的配置:auto_append_file、auto_prepend_file。auto_prepend_file配置的作用為指定一個文件在主文件解析前解析,auto_append_file的作用為指定一個文件在主文件解析后解析,見圖2-4-16。

圖2-4-16
在實際利用時,通常會使用auto_prepend_file。獲取auto_prepend_file、auto_append_file配置信息后,如果prepend_file_p不為空,則先調(diào)用zend_execute_scripts解析prepend_file_p,再調(diào)用zend_execute_scripts解析primary_file(主文件)和append_file_p。
由于append_file_p最后被執(zhí)行,如果在解析primary_file的opcode時出現(xiàn)Fatal error或exit,那么append_file_p不再會被zend_execute_scripts解析。
不過使用.user.ini配置文件繞過上傳黑名單有著很大的局限性。從上可以看出,只有在當(dāng)前目錄下有PHP文件被執(zhí)行時,才會加載當(dāng)前目錄下的.user.ini文件,而在上傳目錄下通常不會存在PHP文件,繞過見圖2-4-17。

圖2-4-17
先上傳配置文件,配置在主文件解析前解析yu.txt文件,見圖2-4-18。上傳yu.txt文件,訪問當(dāng)前目錄下的任意PHP文件,見圖2-4-19。在解析upload.php文件前,先解析yu.txt文件,成功觸發(fā)phpinfo()。

圖2-4-18

圖2-4-19
2.4.4 文件后綴白名單校驗繞過
白名單校驗文件后綴比黑名單校驗更安全、普遍,繞過白名單通常需要借助Web服務(wù)器的各解析漏洞或ImageMagick等組件漏洞。
2.4.4.1 Web服務(wù)器解析漏洞
1.IIS解析漏洞
IIS 6中存在兩個解析漏洞:“*.asp”文件夾下的所有文件會被當(dāng)做腳本文件進(jìn)行解析,文件名為“yu.asp;a.jpg”的文件會被解析為ASP文件,上傳“x.asp,a.jpg”文件獲取到的后綴為jpg,能夠通過白名單的校驗。
2.Nginx解析漏洞
Nginx的解析漏洞為配置不當(dāng)造成的問題,在Nginx未配置try_files且FPM未設(shè)置security.limit_extensions的場景下,可能出現(xiàn)解析漏洞。Nginx的配置如下:

先上傳x.jpg文件,再訪問x.jpg/1.php,location為.php結(jié)尾,會交給FPM處理,此時$fastcgi_script_name的值為x.jpg/1.php;在PHP開啟cgi.fix_pathinfo配置時,x.jpg/1.php文件不存在,開始fallback去掉最右邊的“/”及后續(xù)內(nèi)容,繼續(xù)判斷x.jpg是否存在;這時若x.jpg存在,則會用PHP處理該文件,如果FPM沒有配置security.limit_extensions限制執(zhí)行文件后綴必須為php,則會產(chǎn)生解析漏洞,見圖2-4-20。

圖2-4-20
2.4.4.2 APACHE解析漏洞
1.多后綴文件解析漏洞
在Apache中,單個文件支持擁有多個后綴,如果多個后綴都存在對應(yīng)的handler或media-type,那么對應(yīng)的handler會處理當(dāng)前文件。
在AddHandler application/x-httpd-php.php配置下,x.php.xxx文件會使用application/x-httpd-php處理當(dāng)前文件,見圖2-4-21。

圖2-4-21

在以上Apache配置下,當(dāng)使用AddType(非之前的AddHandler)時,多后綴文件會從最右后綴開始識別,如果后綴不存在對應(yīng)的MIME type或Handler,則會繼續(xù)往左識別后綴,直到后綴有對應(yīng)的MIME type或Handler。x.php.xxx文件由于xxx后綴沒有對應(yīng)的handler或mime type,這時往左識別出PHP后綴,就會將該文件交給application/x-httpd-php處理,見圖2-4-22。如果白名單中存在偏門后綴,那么可以嘗試使用這種方法。
2.Apache CVE-2017-15715漏洞
瀏覽https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15715,根據(jù)該CVE的描述可以看出,在HTTPD 2.4.0到2.4.29版本中,F(xiàn)ilesMatch指令正則中“$”能夠匹配到換行符,可能導(dǎo)致黑名單繞過。

圖2-4-22

以上Apache配置,原意是只解析以.php結(jié)尾的文件,但是由于15715漏洞導(dǎo)致.php\n結(jié)尾的文件也能被解析,那么可以上傳x.php\n文件繞過黑名單。不過在PHP $_FILES上傳的過程中,$_FILES['name']會清除“\n”字符導(dǎo)致不能利用,這里使用file_put_contents實現(xiàn)上傳,測試代碼見圖2-4-23。

圖2-4-23
在以上代碼中,上傳PHP文件失敗,見圖2-4-24。

圖2-4-24
上傳x.php\n文件可以成功,見圖2-4-25。

圖2-4-25
2.4.5 文件禁止訪問繞過
在測試中經(jīng)常會遇到一些允許任意上傳的功能,在訪問上傳的腳本文件時才發(fā)現(xiàn)并不能被解析或訪問,通常是在Web服務(wù)器中配置上傳目錄下的腳本文件禁止訪問。在上傳目錄下的文件無法被訪問時,最好的繞過方法肯定是將目錄穿越上傳到根目錄,如嘗試上傳../x.php等類似文件。但是這種方法對于$_FILES上傳是不能實現(xiàn)的,因為PHP在注冊$_FILES['name']時調(diào)用_basename()方法處理了文件名,見圖2-4-26和圖2-4-27。

圖2-4-26

圖2-4-27
_basename方法會獲得最后一個"/"或"\"后面的字符,所以上傳../x.php文件并不能夠?qū)崿F(xiàn)目錄穿越,因為在經(jīng)過_basename后注冊到_FILES['name']的值為x.php。
2.4.5.1.htaccess禁止腳本文件執(zhí)行繞過
低于9.22版本的jQuery-File-Upload在自帶的上傳腳本(server/php/index.php)中,驗證上傳文件后綴使用的正則為:

也就是允許任意文件上傳。之所以有底氣允許任意文件上傳,是因為在它的上傳目錄下自帶.htaccess文件配置上傳的腳本文件無法被執(zhí)行。


但是從Apache 2.3.9起,AllowOverride默認(rèn)為None,所以在.htaccess下任何指令都不能使用,這里的SetHandler、ForceType指令也就毫無作用,直接上傳PHP文件即被執(zhí)行。后續(xù)官方將正則修改為'accept_file_types'=>'/\.(gif|jpe?g|png)$/i'。
2.4.5.2 文件上傳到OSS
隨著云對象存儲的發(fā)展,越來越多的網(wǎng)站選擇把文件上傳到OSS中。當(dāng)然,上傳到OSS中的腳本文件不會被服務(wù)端解析,所以很多開發(fā)者在文件上傳到OSS時會允許任意文件上傳。雖然服務(wù)端不會解析腳本文件,但是可以通過上傳HTML、SVG等文件讓瀏覽器解析實現(xiàn)XSS。不過XSS在aliyuncs.com域下并沒有什么用。
不過現(xiàn)在OSS都會提供綁定域名功能,見圖2-4-28,很多網(wǎng)站會把OSS綁在自己的二級域名下,這時上傳HTML文件導(dǎo)致的XSS就能利用了,這里不再贅述。

圖2-4-28
2.4.5.3 配合文件包含繞過
在PHP文件包含中,程序一般會限制包含的文件后綴只能為“.php”或其他特定后綴,見圖2-4-29。在00截斷越來越罕見的今天,如果上傳目錄腳本文件無法被訪問或不被解析,見圖2-4-30,那么可以上傳一個PHP文件配合文件包含實現(xiàn)解析,見圖2-4-31。

圖2-4-29

圖2-4-30

圖2-4-30(續(xù))

圖2-4-31
類似的場景還有SSTI,常為用戶選擇可以加載的模板,但是模板文件后綴通常會被寫死,所以這時可以通過任意文件上傳模板文件,然后渲染上傳的模板實現(xiàn)SSTI。例如:http://www.yulegeyu.com/2019/02/15/Some-vulnerabilities-in-JEECMSV9/。
2.4.5.4 一些可被繞過的Web配置
上傳目錄中禁止文件執(zhí)行通常在Web服務(wù)器中配置,在不當(dāng)配置下可能存在繞過。
1.pathinfo導(dǎo)致的繞過問題
Nginx的配置如下:

由于pathinfo在各大框架的流行,很多計算機(jī)支持pathinfo,會把location類似x.php/xxxx的路徑也交給FPM解析,但是x.php/xxx并不符合deny all的匹配規(guī)則,導(dǎo)致繞過,見圖2-4-32。

圖2-4-32
2.location匹配順序?qū)е碌睦@過問題
在Nginx配置中經(jīng)常出現(xiàn)多個location都能匹配請求URI的場景,這時具體交給哪個location語句塊處理,就需要看location塊的匹配優(yōu)先級。Nginx配置如下:

Nginx的location塊匹配優(yōu)先級為先匹配普通location,再匹配正則location。如果存在多個普通location都匹配URI,則會按照最長前綴原則選擇location。在普通location匹配完成后,如果不是完全匹配,那么并不會結(jié)束,而是繼續(xù)交給正則location檢測,如果正則匹配成功,就會覆蓋普通location匹配的結(jié)果。所以在以上配置中,deny all被正則location匹配所覆蓋,upload目錄下的PHP文件依舊能夠正常執(zhí)行,見圖2-4-33。


圖2-4-33
正確的配置方法應(yīng)該在普通匹配前加上“^~”,表示只要該普通匹配成功,就算不是完全匹配也不再進(jìn)行正則匹配,所以在該配置下能夠成功禁止PHP文件的解析,見圖2-4-34。


圖2-4-34
以上配置與普通匹配不同,正則location只要匹配成功,就不再考慮后面的location塊。正則location匹配順序與在配置文件中的物理順序有關(guān),物理順序在前的會先進(jìn)行匹配。所以在以上的配置中,兩個匹配都為正則匹配,那么按照匹配順序upload目錄下的PHP文件依舊會交給FPM解析,見圖2-4-35。

圖2-4-35
3.利用Apache解析漏洞繞過

Apache通常使用以上配置禁止上傳目錄中的腳本文件被訪問,此時可以利用Apache的解析漏洞上傳yu.php.aaa文件,使其不符合deny all的匹配規(guī)則實現(xiàn)繞過,見圖2-4-36。

圖2-4-36
2.4.6 繞過圖片驗證實現(xiàn)代碼執(zhí)行
部分開發(fā)者認(rèn)為,上傳文件的內(nèi)容如果是一張正常的圖片就不可能再執(zhí)行代碼,所以允許任意后綴文件上傳,但是在PHP中,檢測文件是否為正常圖片的方法往往能被繞過。
1.getimagesize繞過
getimagesize函數(shù)用來測定任何圖像文件的大小并返回圖像的尺寸以及文件類型,如果文件不是有效的圖像文件,則將返回FALSE并產(chǎn)生一條E_WARNING級錯誤,見圖2-4-37。

圖2-4-37
嘗試直接上傳PHP文件失敗,見圖2-4-38。

圖2-4-38
getimagesize的繞過比較簡單,只要將PHP代碼添加到圖片內(nèi)容后就能成功繞過,見圖2-4-39,此時上傳的PHP文件能夠正常解析,見圖2-4-40。

圖2-4-39

圖2-4-40
同時,getimagesize支持測定XBM格式圖片——一種純文本圖片格式。getimagesize在測定XBM時會逐行讀取XBM文件,如果某一行符合#define%s%d,就會格式化取出字符串和數(shù)字。如果最后height和width不為空,那么getimagesize就會測定成功。因為是逐行讀取,所以height和width可以放到任意一行。


使用XBM可以通過getimagesize驗證并且同時利用imagemagick。

2.imagecreatefromjpeg繞過
imagecreatefromjpeg方法會渲染圖像生成新的圖像,在圖像中注入腳本代碼經(jīng)過渲染后,腳本代碼會消失,不過該方法也已經(jīng)存在成熟的繞過腳本:https://github.com/BlackFan/jpg_payload。測試代碼見圖2-4-41。

圖2-4-41
繞過需要先上傳正常圖片文件,再下載回渲染后的圖片,運(yùn)行jpg_payload.php處理下載回來的圖片,將代碼注入圖片文件,然后上傳新生成的圖片,能看出經(jīng)過imagecreatefromjpeg后注入的腳本代碼依然存在,見圖2-4-42。

圖2-4-42
2.4.7 上傳生成的臨時文件利用
PHP在上傳文件過程中會生成臨時文件,在上傳完成后會刪除臨時文件。在存在包含漏洞卻找不到上傳功能且無文件可包含時,可以嘗試包含上傳生成的臨時文件配合利用。

圖2-4-42
1.LFI via phpinfo
由于上傳生成的臨時文件的文件名存在6位隨機(jī)字符,并且在上傳完成后會刪除該文件,因此在有限的時間內(nèi)找到臨時文件名是一個很大的問題。不過phpinfo中會輸出當(dāng)前環(huán)境下的所有變量,如果存在$_FILES變量,也會輸出,所以如果目標(biāo)存在phpinfo文件,往phpinfo上傳一個文件,就可以輕松拿到tmp_name,見圖2-4-43。LFI配合phpinfo場景已經(jīng)存在成熟的利用腳本了,這里不再贅述。

圖2-4-43
2.LFI via Upload_Progress
當(dāng)session.upload_progress.enabled選項開啟時,PHP能在每個文件上傳時監(jiān)測上傳進(jìn)度。從PHP 5.4起,該配置可用且默認(rèn)開啟。當(dāng)上傳文件時,同時POST與INI中設(shè)置的session.upload_progress.name同名變量,PHP檢測到這種POST請求時,會往Session中添加一組數(shù)據(jù),寫入上傳進(jìn)度等信息,其索引為session.upload_progress.prefix與$_POST[session.upload_progress.name]值連接在一起的值。session.upload_progress.prefix默認(rèn)為upload_progress_,session.upload_progress.name默認(rèn)為php_session_upload_progress,所以上傳時需要POST php_session_upload_progress。這時上傳文件名會寫入SESSION,PHPSESSION默認(rèn)以文件保存,進(jìn)而可以配合LFI,見圖2-4-44。

圖2-4-44
由于session.upload_progress.cleanup配置默認(rèn)為ON,即在讀取完P(guān)OST數(shù)據(jù)后會清除upload_progress所添加的Session,因此這里需要用到條件競爭,在Session文件被清除前包含到Session文件,最終實現(xiàn)代碼執(zhí)行。條件競爭結(jié)果見圖2-4-45。

圖2-4-45
3.LFI via Segmentation fault
Segmentation fault方法實現(xiàn)思路為,向出現(xiàn)Segmentation fault異常的地址上傳文件,導(dǎo)致在垃圾回收前異常退出,上傳生成的臨時文件就不會被刪除,最后通過大量上傳文件同時枚舉臨時文件名的所有可能,最終實現(xiàn)LFI的利用,見圖2-4-44。在PHP 7中,如果用戶可以控制file函數(shù)的參數(shù),即可產(chǎn)生Segmentation fault。至于Segfault形成原因,可以直接看Nu1L戰(zhàn)隊隊員wupco的分析:https://hackmd.io/s/Hk-2nUb3Q。
2.4.8 使用file_put_contents實現(xiàn)文件上傳
除了使用FILES實現(xiàn)上傳,在測試中也會遇到另一種上傳格式,這種方法通常在獲取到文件內(nèi)容后使用file_put_contents等方法實現(xiàn)文件上傳,見圖2-4-46。

圖2-4-46
1.file_put_contents上傳文件黑名單繞過
在文件名可控場景下,F(xiàn)ILES上傳中即使開發(fā)者沒有過濾“/../”字符,PHP在注冊FILES['name']變量時也會自身做_basename處理,導(dǎo)致用戶不能傳入“/../”等字符。在file_put_contents方法中,文件地址參數(shù)可能為絕對路徑,所以PHP肯定不會對該參數(shù)做basename處理,在文件名可控情況下,file_put_contents上傳文件能夠?qū)崿F(xiàn)目錄穿越。
當(dāng)圖2-4-47所示代碼出現(xiàn)在Nginx+PHP環(huán)境且upload目錄下無可執(zhí)行文件時,需要找到其他方法繞過黑名單。file_put_contents的文件名為“yu.php/.”時,能夠正常寫入yu.php文件,并且代碼獲取的后綴為空字符串,所以能夠繞過黑名單,見圖2-4-48。

圖2-4-47

圖2-4-48
當(dāng)用file_put_contents時,zend_virtual_cwd.c的virtual_file_ex方法中調(diào)用tsrm_realpath_r方法標(biāo)準(zhǔn)化路徑。file_put_contents方法的部分調(diào)用棧如下。

在tsrm_realpath_r方法中添加如下代碼:


在該方法中,如果路徑以“/.”結(jié)尾,就會把len定義為“/”字符的索引,然后執(zhí)行:

截斷掉“/.”字符,處理成正常的路徑。不過這種方法只能新建文件,在覆蓋一個存在的文件時會出現(xiàn)錯誤,見圖2-4-49。

圖2-4-49
同樣,在tsrm_realpath_r方法中存在以下代碼:

php_sys_lstat為lstat方法的宏定義,lstat方法用于獲取文件的信息,執(zhí)行失敗則返回-1,執(zhí)行成功則返回0。所以當(dāng)文件不存在時,lstat返回-1,進(jìn)入if語句塊,save變量被重置為0,文件存在時lstat返回0,不進(jìn)入if語句塊,save變量依舊為1。
當(dāng)save變量為1時,進(jìn)入以下語句塊:

在最初判斷路徑末尾為“/.”后,is_dir被賦值為1。不過在截斷“/.”字符后lstat獲取的路徑信息不再是目錄而是文件,即directory為0。is_dir和directory兩者不相同的情況下會返回-1。

當(dāng)返回值為-1時,定義錯誤號碼,最終寫文件失敗。
2.死亡之die繞過
很多網(wǎng)站會把Log或緩存直接寫入PHP文件,為了防止日志或緩存文件執(zhí)行代碼,會在文件開頭加入<?php exit();?>。在圖2-4-50代碼中,用戶可以完全控制filename,包括協(xié)議。

圖2-4-50
在官方手冊(見https://www.php.net/manual/zh/filters.string.php)中可以發(fā)現(xiàn)存在許多過濾器,所以這里可以使用一些字符串過濾器把exit()處理掉,從而讓后面寫入的代碼能夠被執(zhí)行,可以使用base64_decode進(jìn)行處理。


PHP的base64_decode方法默認(rèn)非嚴(yán)格模式,除了跳過填充字符“=”,如果存在字符使得base64_reverse_table[ch]<0,也會跳過。

從base64_reverse_table中可以發(fā)現(xiàn),只有當(dāng)字符的ASCII值為43、47~57、65~90、97~122時,才有base64_reverse_table[ch]>=0,對應(yīng)的字符為+、/、0~9、a~z、A~Z,其余字符都會被跳過。“<?php exit();?>\n”除去了被跳過的字符,剩余phpexit,在base64解碼時每4字節(jié)一組,所以需要再填充1字節(jié),最終被解碼為亂碼后面的代碼就能正常執(zhí)行,見圖2-4-51。

圖2-4-51
2.4.9 ZlP上傳帶來的上傳問題
為了實現(xiàn)批量上傳,很多系統(tǒng)支持上傳ZIP壓縮包,再在后端解壓ZIP文件,如果沒有對解壓出來的文件做好處理,就會導(dǎo)致安全問題,以前PHPCMS就出現(xiàn)過未處理好上傳的ZIP導(dǎo)致的安全問題。
1.未處理解壓文件
圖2-4-52中的代碼僅在上傳時限制文件后綴必須為zip,但是沒有對解壓的文件做任何處理,所以把PHP文件壓縮為ZIP文件,再上傳ZIP文件,后端解壓后實現(xiàn)任意文件上傳,見圖2-4-53。

圖2-4-52

圖2-4-53
2.未遞歸檢測上傳目錄導(dǎo)致繞過
為了解決解壓文件帶來的安全問題,很多程序會在解壓完ZIP后,檢測上傳目錄下是否存在腳本文件,如果存在,則刪除。
例如,圖2-4-54中的代碼在解壓完成后,會通過readdir獲取上傳目錄下的所有文件、目錄,如果發(fā)現(xiàn)后綴不是jpg、gif、png的文件,則刪除。但是以上代碼僅僅檢測了上傳目錄,沒有遞歸檢測上傳目錄下的所有目錄,所以如果解壓出一個目錄,那么目錄下的文件不會被檢測到。雖然hello目錄的后綴不在白名單列表中,但是unlink一個目錄不會成功,僅會拋出warning,所以目錄和目錄下的文件就被保留了,見圖2-4-55。

圖2-4-54

圖2-4-55
當(dāng)然,也可以在壓縮包內(nèi)新建目錄x.jpg,直接跳過unlink,連warning都不會拋出,見圖2-4-56。

圖2-4-56
3.條件競爭導(dǎo)致繞過
在圖2-4-57所示的代碼中,遞歸檢測了上傳目錄下的所有目錄,所以之前的繞過方式不再可行。

圖2-4-57
這種場景下可以通過條件競爭的方式繞過,即在文件被刪除前訪問文件,生成另一個腳本文件到非上傳目錄中,見圖2-4-58和圖2-4-59。

圖2-4-58

圖2-4-59
通過不斷上傳文件與訪問文件,在文件被刪除前訪問到了文件,最終生成腳本文件到其他目錄中實現(xiàn)繞過,見圖2-4-60。

圖2-4-60
4.解壓產(chǎn)生異常退出實現(xiàn)繞過
為了避免條件競爭問題,圖2-4-61中的代碼把文件解壓到了一個隨機(jī)目錄中,由于目錄名不可預(yù)測,因此不再能夠進(jìn)行條件競爭。ZipArchive對象中的extractTo方法在解壓失敗時會返回false,很多程序在解壓失敗后會立即退出程序,但是其實可以構(gòu)造出一種解壓到一半然后解壓失敗的ZIP包。使用010 Editor修改生成的ZIP包,將2.php后的內(nèi)容修改為0xff然后保存生成的新ZIP文件,見圖2-4-62。
由于解壓失敗,在check_dir方法前執(zhí)行了exit,已解壓出的腳本文件就不會被刪除。這時再枚舉目錄的所有可能,最終跑到腳本文件,見圖2-4-63。
5.解壓特殊文件實現(xiàn)繞過
為了修復(fù)異常退出導(dǎo)致的繞過,將代碼修改為以下代碼,在解壓失敗后也會調(diào)用check_dir方法刪除目錄下的非法文件,所以這時使用異常退出方法也不再可行。


圖2-4-61

圖2-4-62

圖2-4-63
在以上場景中,如果在解壓ZIP文件時能夠讓解壓出的文件名含有“../”字符實現(xiàn)目錄穿越跳出上傳目錄,那么解壓出的腳本文件不會被check_dir刪除。PHP解壓ZIP文件有兩種常用方法,一種是PHP自帶的擴(kuò)展ZipArchive,另一種是第三方的PclZip。
首先測試ZipArchive,構(gòu)造一個含有“../”字符的壓縮包,生成一個正常壓縮包,然后使用010 editor修改壓縮包文件,見圖2-4-64。

圖2-4-64
上傳該ZIP文件后,解壓出的文件依舊在隨機(jī)目錄下,沒有實現(xiàn)目錄穿越,見圖2-4-65。
在/ext/zip/php_zip.c文件中,ZIPARCHIVE_METHOD(extractTo)方法調(diào)用了php_zip_extract_file方法來解壓文件。


圖2-4-65

在php_zip_extract_file方法中,先使用virtual_file_ex對路徑規(guī)范化,從注釋中也能看出規(guī)范化后的結(jié)果,再調(diào)用php_zip_make_relative_path將路徑處理為相對路徑。
例如,壓縮包中含有/../aaaaaaaaa.php文件,先經(jīng)過virtual_file_ex方法中tsrm_realpath_r處理后,變?yōu)?aaaaaaaaa.php,再經(jīng)過php_zip_make_relative_path處理,變?yōu)橄鄬β窂絘aaaaaaaa.php,因而不能夠?qū)崿F(xiàn)目錄穿越。不過Windows下的virtual_file_ex和Linux處理不同,Windows中不會使用tsrm_realpath_r方法處理路徑,所以在Windows下可以使用這種方法,具體代碼可查看zend/zend_virtual_cwd.c文件。
另一種解壓ZIP的常用方法是,PclZip沒有規(guī)范化路徑,所以可以實現(xiàn)目錄穿越。測試代碼見圖2-4-66。

圖2-4-66

PclZip構(gòu)造壓縮包時,需要注意包內(nèi)的第一個文件應(yīng)該是正常文件,如果第一個文件是目錄,那么穿越文件在Linux下利用會失敗。主要原因是文件寫入臨時目錄時,會使用privDirCheck方法判斷目錄是否存在,如果不存在,就會遞歸創(chuàng)建目錄。
假設(shè)生成的臨時目錄為dd409260aea46a90e61b9a69fb9726ef,壓縮包內(nèi)的第一個文件為/../../a.php。開始進(jìn)入privDirCheck目錄檢測、創(chuàng)建流程,由于dd409260aea46a90e61b9a69fb-9726ef目錄不存在,Linux下不存在的目錄不能穿越,因此

方法會返回false。
privDirCheck方法的大概流程如下。
<1>is_dir('./upload/dd409260aea46a90e61b9a69fb9726ef/../..')返回false,獲取父目錄./upload/dd409260aea46a90e61b9a69fb9726ef/..,調(diào)用privDirCheck方法。
<2>is_dir('./upload/dd409260aea46a90e61b9a69fb9726ef/..')依然返回false,獲取父目錄./upload/dd409260aea46a90e61b9a69fb9726ef,調(diào)用privDirCheck方法
<3>is_dir('./upload/dd409260aea46a90e61b9a69fb9726ef')依然返回false,獲取父目錄./upload,調(diào)用privDirCheck方法。
<4>is_dir('./upload')目錄存在,返回true,然后開始遞歸創(chuàng)建不存在的子目錄。
<5>mkdir('./upload/dd409260aea46a90e61b9a69fb9726ef'),成功創(chuàng)建dd40目錄。
<6>mkdir('./upload/dd409260aea46a90e61b9a69fb9726ef/..'),目錄穿越成功,實際執(zhí)行的為mkdir('./upload')。由于upload目錄已存在,則出現(xiàn)錯誤,返回錯誤編號,最終從壓縮包中提取文件失敗。
綜上,需要壓縮包的第一個文件是正常文件,則先創(chuàng)建臨時目錄,后面的文件目錄穿越不會再出現(xiàn)問題。當(dāng)然,Windows下就算目錄不存在也可以目錄穿越,不需要考慮這個問題。
構(gòu)造一個含有特殊文件的壓縮包進(jìn)行上傳,見圖2-4-67,最終實現(xiàn)了利用,見圖2-4-68。

圖2-4-67

圖2-4-68
- Android應(yīng)用安全實戰(zhàn):Frida協(xié)議分析
- RESTful Java Web Services Security
- Extending Symfony2 Web Application Framework
- 等級保護(hù)測評理論及應(yīng)用
- Kali Linux Wireless Penetration Testing Cookbook
- 數(shù)據(jù)安全與隱私計算(第3版)
- Testing and Securing Android Studio Applications
- CTF競賽權(quán)威指南(Pwn篇)
- 計算機(jī)網(wǎng)絡(luò)安全基礎(chǔ)(第5版)
- 隱私計算
- 解密數(shù)據(jù)恢復(fù)
- 網(wǎng)絡(luò)空間安全:拒絕服務(wù)攻擊檢測與防御
- Mastering Metasploit
- 黑客攻防從入門到精通:命令版
- Real-World SRE