當時樓主正准備收拾東西回家,看到這個新聞心里一驚:失傳江湖多年的0字符截斷上傳漏洞又重現了?而且還影響這么多版本!如果漏洞屬實,看來今晚又要通宵打補丁了啊。
不過經過簡單分析后,發現漏洞的利用條件相當苛刻(很多人好奇到底有多苛刻),樓主簡單記錄自己的分析過程和大家分享一下,如有不當,請多多指正。
一、漏洞概述
漏洞報告者說php的上傳函數 move_uploaded_file的目的路徑參數可以使用空字符截斷,繞過jpg,png上傳類型的檢測,從而導致任意文件上傳。報告者給出的測試EXP:
move_uploaded_file($_FILES['x']['tmp_name'],"/tmp/test.php\x00.jpg")
按照漏洞報告者的描述 5.3 以后版本都受影響(https://bugs.php.net/bug.php?id=69207)
二、漏洞測試
為驗證漏洞有效性樓主搭建一個測試環境,版本 5.3.6,構造測試代碼如下:
上傳前台頁面upload.htm:
上傳處理腳本upload.php
測試上傳upload.php始終返回失敗false
於是換了一個版本5.3.29,依舊未測試成功,難道是我測試的方法不對,那就看一下源碼吧。
三、漏洞調試
move_uploaded_file的代碼實現在 ext/standard/basic_functions.c 文件,關鍵代碼:
代碼中有幾處return false的邏輯,那么測試不成功肯定是命中了其中的一個邏輯。
樓主使用最原始的打log方法來調試,確認測試代碼被上述標紅代碼過濾了,於是查看打印出來strlen(new_path) 和 new_path_len的值 —— 分別是19 和 23。
new_path 是路徑名字符串“/tmp/whoisdashuaige\x00jpg”的長度,由於strlen計算到空字符處\x00結束,因此長度19可以理解。
那么為什么new_path_len是23呢?
PHP語言中所有的變量類型都保存在一個如下的結構體中,new_path_len對應標紅的len。這里的len是一個二進制長度,不會遇到空字符結束所以長度為23。所以測試demo使用\0在這里長度不等一直返回失敗。
那測試成功的又是什么情況呢,組里另外一位同事發來消息說在5.6.6版本可測試成功,打開5.6.6漏洞代碼處一看震驚了,5.6.6代碼如下:
原來在高版本(受影響版本中),PHP把長度比較的安全檢查邏輯給去掉了,導致了漏洞的發生,不知道這段代碼是哪個開發者更新的,什么仇什么怨。
四、漏洞利用條件
漏洞利用需要控制第二個參數(目標路徑),比如:
但是,實際在開發場景中,目標路徑一般是程序自己生成的(避免同名文件覆蓋的情況),就算退一步,程序猿同學想取原來的文件名,往往也習慣用$_FILES變量取文件名。
而$_FILES變量中如果加入\0字符截斷,卻又不影響程序邏輯:
提交a.php\0jpg 和 a.php 是等價的。
測試方法如下:
上傳抓包修改name為a.php\0jpg(\0是nul字符),可以看到$_FILES['xx']['name']存儲的字符串是a.php,不會包含\0截斷之后的字符,因此並不影響代碼的驗證邏輯。
但是如果通過$_REQUEST方式獲取的,則可能出現擴展名期望值不一致的情況,造成“任意文件上傳”。
五、總結
1、漏洞影響版本必須在5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7,詳見CVE公告:https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2348
2、漏洞利用條件苛刻,與實際業務書寫代碼習慣有較大的差異,因此風險較低
行為倉促,如有不當請指正。
摘自:TSRC