ThinkPHP 5.1.x SQL注入漏洞分析
一、背景介紹
ThinkPHP 是一個快速、簡單的基於 MVC 和面向對象的輕量級 PHP 開發框架,遵循 Apache2 開源協議發布。ThinkPHP從誕生以來一直秉承簡潔實用的設計原則,在保持出色的性能和至簡的代碼的同時,也注重開發體驗和易用性,為 WEB 應用和 API 開發提供了強有力的支持。
在近期,ThinkPHP 框架被曝出存在SQL注入漏洞。由於SQL注入漏洞的危害性以及該框架應用十分廣泛。 對此,天融信阿爾法實驗室以靜態和動態兩種方式對該漏洞進行了深入分析。
1.1 漏洞描述
在ThinkPHP5.1.23之前的版本中存在SQL注入漏洞,該漏洞是由於程序在處理order by 後的參數時,未正確過濾處理數組的key值所造成。如果該參數用戶可控,且當傳遞的數據為數組時,會導致漏洞的產生。
1.2 受影響的系統版本
ThinkPHP < 5.1.23
1.3 漏洞編號
CVE-2018-16385
二、環境搭建
1.下載安裝thinkphp5.1.x
對於thinkphp5.1.x完整版,目前官方沒有直接下載的鏈接。Github上只是放出核心版。該版本需要以Composer或Git方式進行安裝。
這裡以Composer安裝方式說明。
在 Linux 和 Mac OS X 中可以運行如下命令:
curl -sS https://getcomposer.org/installer | phpmv composer.phar/usr/local/bin/composer
在 Windows 中,你需要下載並運行 Composer-Setup.exe 。
安裝好之後,切換路徑到WEB目錄下運行:
composercreate-project topthink/think=5.1.1 tp5.1 --prefer-dist
然後會生成一個名為tp5.1的文件夾。到此think5.1.1下載成功。
2.然後在瀏覽器中訪問
如果出現該頁面,則證明安裝成功。
3.Demo示例
編寫Demo文件,並將文件命名為Test.php,然後放在/tp5.1/application/index/controller/目錄下。
4.資料庫
與Demo文件匹配,需要創建一個user表,然後設一個欄位(id)。
三、漏洞細節
在/thinkphp/library/think/db/Builder.php parseOrder()的函數中:
通過Demo傳入order參數內容,當傳入的$order是一個數組時,foreach函數將$order數組分為key和value形式。
根據漏洞修復補丁,知道漏洞發生在parseOrderField()函數中。
當$val為數組時,會進入parseOrderField()函數。
跟蹤parseOrderField()函數
getOptions()函數是獲取了當前要查詢的參數,getFieldsBind()函數是獲取數據表綁定信息,foreach循環是對$val值進行了處理,這裡其實不是重點,就提一下。$val值是什麼不用管,因為注入點在$key上,而$val 拼接在$key後面,可以在構造$key加#注釋掉$val。
重點是parseKey()函數,跟蹤parseKey()函數。
這裡對傳入的$key進行多重判斷以及處理。
1. is_numeric判斷,如果是數字,則返回,不是的話繼續向下執行。
2. 判斷$key是否屬於Expression類。
3. strpos($key, 『->』) && false ===strpos($key, 『(『) 。
4. (『*』 != $key && ($strict ||!preg_match(『/[,""*()`.s]/』, $key)))。
因為$key是我們的sql注入語句,所以1.2.3肯定不滿足,而4滿足。
所以此時的$key會在左右兩側加個 ` 號。
$table不存在,不會對$key修改,所以加 ` 號後會返回$key,然後和$val以及field字元串進行拼接,再然後return賦值給array變數,緊接著,array與order by 字元串進行拼接形成order by查詢語句,最終系統調用query()函數進行資料庫查詢,觸發漏洞。
著重說明一下,這裡由於field函數,漏洞利用有兩個關鍵點:
首先解釋下field()函數:MySQL中的field()函數作用是對SQL中查詢結果集進行指定順序排序。一般與order by 一起使用。
關鍵點1:
field()函數必須指定大於等於兩個欄位才可以正常運行,否則就會報錯。
當表中只有一個欄位時,我們可以隨意指定一個數字或字元串的參數。
關鍵點2:
當field中的參數不是字元串或數字時,指定的參數必須是正確的表欄位,否則程序就會報錯。這裡由於程序會在第一個欄位中加 `` 限制 ,所以必須指定正確的欄位名稱。第二個欄位沒有限制,可以指定字元串或數字。
所以,我們要利用該漏洞,第一我們至少需要知道表中的一個欄位名稱,第二向field()函數中中傳入兩個欄位。第二個欄位不需要知道欄位名,用數字或字元串繞過即可。
Payload構造
根據以上分析,構造payload需要滿足以下條件:
1.傳入的$order需要是一個數組。
2.$val 必須也是數組。
3.至少知道資料庫表中的一個欄位名稱,並且傳入兩個參數。
4.閉合 ` 。
最終Payload構造如下:
http://127.0.0.1/tp5.1/public/index/test/index?order[id`,111)|updatexml(1,concat(0x3a,user()),1)%23][]=1
或
http://127.0.0.1/tp5.1/public/index/test/index?order[id`,"aaa")| updatexml(1,concat(0x3a,user()),1)%23][]=1
四、動態調試分析
有時候單單靜態分析,很難知道某些函數做了些什麼,而對於程序運行過程,也很難理解透徹。而利用動態分析,一步一步debug,就很容易理解清楚。
這裡我們利用上面構造的payload進行debug。
首先下斷點:
$val是個數組,進入parseOrderField()函數。F7下一步(我這裡用的是phpstorm,F7是單步調試的意思)。
foreach循環後,可以看到只是處理了$val,並沒有涉及$key(我們的關注點在$key)。$val 的值是在$key的基礎上加了個:data__前綴,後面加了個0。
繼續F7,進入了parseKey()函數。
到這裡,看到$key滿足if條件,然後兩邊加了個 ` 號。
最後返回的$key。其實就是兩邊加了個 ` 號 。
最後,返回給array變數的值為file字元串val的拼接。
繼續F7,看看接下來程序怎麼走。
進行了limit分析,union分析等多個分析處理,最終來到了removeoption()函數。
可以看到這裡的$sql,已經可以觸發注入漏洞。
然後經過了中間的幾個過程,對比上圖,並沒有改變sql語句內容。最後sql語句,進入query()函數執行了SQL語句,成功觸發注入漏洞。
五、修復建議
官方補丁
目前官方已經更新補丁,請受影響的用戶儘快升級到ThinkPHP5.1.24版本。
手工修復
根據官方給出的方案進行代碼修改。
https://github.com/top-think/framework/commit/f0f9fc71b8b3716bd2abdf9518bcdf1897bb776
*本文作者:alphalab,轉載請註明來自FreeBuf.COM
※淺談PHP安全規範
※XPwn 2018探索未來安全盛會,萬物互聯時代可以玩出哪些花活?
TAG:FreeBuf |