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 : "";