1.環境:
php5.5.38+apache+seacms v6.45
seacms目錄結構:
│─admin //後臺管理目錄 │ │─coplugins //已停用目錄 │ │─ebak //帝國備份王資料備份 │ │─editor //編輯器 │ │─img //後臺靜態檔案 │ │─js //後臺js檔案 │ │─templets //後臺模板檔案 │─article //文章內容頁 │─articlelist //文章列表頁 │─comment //評論 │ │─api //評論介面檔案 │ │─images //評論靜態檔案 │ │─js //評論js檔案 │─data //配置資料及快取檔案 │ │─admin //後臺配置儲存 │ │─cache //快取 │ │─mark //水印 │ │─sessions //sessions檔案 │─detail //視訊內容頁 │─include //核心檔案 │ │─crons //定時任務配置 │ │─data //靜態檔案 │ │─inc //擴充套件檔案 │ │─webscan //360安全監測模組 │─install //安裝模組 │ │─images //安裝模組靜態檔案 │ │─templates //安裝模組模板 │─js //js檔案 │ │─ads //預設廣告目錄 │ │─player //播放器目錄 │─list //視訊列表頁 │─news //文章首頁 │─pic //靜態檔案 │ │─faces //表情影象 │ │─member //會員模組介面 │ │─slide //舊版Flash幻燈片 │ │─zt //專題靜態檔案 │─templets //模板目錄 │─topic //專題內容頁 │─topiclist //專題列表頁 │─uploads //上傳檔案目錄 │─video //視訊播放頁 │─weixin //微信介面目錄 └─index.php //首頁檔案
2.利用程式碼
poc1
http://seacms.test/search.php POST: searchtype=5&order=}{end if} {if:1)phpinfo();if(1}{end if}
poc2:
POST: searchtype=5&order=}{end if}{if:1)$_POST[func]($_POST[cmd]);//}{end if}&func=system&cmd=whoami
searchtype=5&order=}{end if}{if:1)$_POST[func]($_POST[cmd]);if(1}{end if}&func=system&cmd=whoami
3.執行效果
4.漏洞分析
漏洞產生鏈如上圖所示,在search.php的212行下斷點,因為在此處產生了parseIf()函式的呼叫,並且最終的命令執行是發生在此函式中,用payload打一次,將停在此處,進入此函式進行分析
如上圖所示,其中buildregx函式是構建php的原生正規表示式
接下來使用$labelRule規則進行preg_match_all匹配出了所有滿足的結果,並放在$iar中,我猜測這裡class頂一個兩個css樣式,通過if條件來調整按鈕樣式的
通過這4行程式碼將$iar中的每條記錄分為條件,以及條件體
接著判斷正則$labelRule2所表示的字串是否包含於條件體中,預設是不包含的,並且$labelRule3中包含的{else}字串是出現在條件體中的,所以進入迴圈,此時將條件體又分為兩塊,
分別代表兩種不同的css樣式,接著就是觸發漏洞的核心,在這裡也發現了eval函式的呼叫,用於程式碼執行的經典函式
如上圖所示,將$strif變數與if條件進行了拼接,那麼此處是否存在程式碼注入的情況?的確如此,此時可以看看$strif的值
其中第95條就包含有我們的payload,那麼此時將payload和if條件進行拼接可以得到:
if(1)phpinfo();if(1)
此時成功閉合了php語句,並且跟後面的$ifFlag條件體也成功閉合了,所以能夠成功進行程式碼執行!!!程式碼注入真刺激~,到此已經實現RCE,那麼想想為啥會造成這樣的漏洞,我向上看看變數是如何傳遞過來的,
parse函式就是在main.class.php這個類檔案中定義的處理if程式碼塊的函式,其入口引數為$content,那麼回到呼叫parseIf函式的地方,也就是search.php,因為我們漏洞檔案也在該檔案,那麼我們POST傳遞過來的payload最終會傳遞到content然後再進入到parseIf函式進行處理,而該處呼叫又是存在於echoPageSearch()函式中,那麼回到該函式入口處,在其上方發現了對其的呼叫,現在在此檔案全域性搜尋以下POST字串
如上圖所示,沒有搜尋到POST,那麼有可能包含在common.php中,進去看看
正與我們所猜測的一樣,此時可以在註釋中發現,其通過一段迴圈將POST中的值註冊為變數了,並且我們提交的標量裡不能包含cfg_和GLOVALS字串,並且在COOKIE中不能夠設定,這裡是為了防止變數覆蓋
接下來還呼叫了_RunMagicQuotes()函式對變數值進行過濾,實際上進行了一個addslashes()函式的操作,並不影響我們實際所用的payload
可以從上圖看到傳遞進來的變數直接註冊為內部的變數了,並且變數內容沒有發生變化,那麼說明都是我們可以控制的,因為最後parse處理的變數是$content,那麼我們需要弄清$order是如何賦值到$content中的,
再次檔案中搜尋$order的使用
可以找到4處呼叫,第一處是在函式內部global來引用,第二處又將$order賦值給$orderStr,但是這樣賦值沒有影響到$content變數,因此看最後一處呼叫
如上圖所示,將$content中的{searchpage:ordername}部分替換為了$order變數的值,為了更清楚的看看$content的內容是如何變化的,我們可以在158行和160行處下斷點,重新執行一次payload,此時可以在此斷點處檢視$content的值,並將替換前後的$content值進行對比,可以看到str_replace()函式將進行3處替換
即在此處完成了payload對$content變數的注入,之後在最終呼叫parseIf()函式處理$content變數之前,又對$content的內容進行了多次替換,但是並沒有影響到我們的payload,接下來看看程式是如何解析出來我們的paylaod的
上面已經說過程式是利用
{if:(.*?)}(.*?){end if}
這一串正則來對$content變數進行匹配的,那麼此時對於我們注入payload的部分,最終是eval()中包含$strIf變數,那麼由於是貪婪匹配,所以首先將會匹配到第一部分{if:"}{end if} 將“匹配出來作為一部分,然後下一次匹配再匹配到1)phpinfo();if(1作為另一次匹配的部分
可以從$iar變數的值中驗證我們的推理是正確的,這裡存在3處相同的匹配是因為之前$order對$content進行了3次payload注入,這樣的構造的確很巧妙,首先1)phpinfo();if(1這一部分要閉合後面eval部分,然後再要滿足前面正則匹配的邏輯能夠把payload完整匹配出來,
這可能也是開發人員沒有想到的,之後拼接的方法上面已經講了,漏洞的整個分析流程到此結束。
5.修復方法:
在64行新增
$order = ($order == "commend" || $order == "time" || $order == "hit") ? $order : "";