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()函式停止資料庫查詢,觸發破綻。BT天堂
著重闡明一下,這裡由於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字串和$key以及$val的拼接。
持續F7,看看接上去順序怎樣走。
停止了limit剖析,union剖析等多個剖析處置,最終離開了removeoption()函式。
可以看到這裡的$sql,曾經可以觸發注入破綻。
然後經過了兩頭的幾個程序,比照上圖,並沒有改動sql語句內容。最初sql語句,進入query()函式執行了SQL語句,成功觸發注入破綻。
五、修復建議
官方補丁
目前官方曾經更新補丁,請受影響的使用者儘快晉級到ThinkPHP5.1.24版本。
手工修復
依據官方給出的方案停止程式碼修正。
https://github.com/top-think/framework/commit/f0f9fc71b8b3716bd2abdf9518bcdf1897bb776
*本文作者:alphalab,轉載請註明來自FreeBuf.COM