Author: Badcode@知道創宇404實驗室
Date: 2018/09/04
背景
ECShop是一款B2C獨立網店系統,適合企業及個人快速構建個性化網上商店。系統是基於PHP語言及MYSQL數據庫構架開發的跨平台開源程序。2018年6月13日,知道創宇404積極防禦團隊通過知道創宇旗下雲防禦產品“創宇盾”防禦攔截並捕獲到一個針對某著名區塊鏈交易所網站的攻擊,通過分析,發現攻擊者利用的正式ECShop 2.x版本的0day漏洞攻擊。於2018年6月14日,提交到知道創宇Seebug漏洞平台並收錄。
隨後於2018年8月31日,ID為“ringk3y”研究人員在其博客公開這個漏洞,並做了詳細分析,該分析收錄在Seebug Paper。
知道創宇404積極防禦團隊於2018年9月2日正式對外發布《ECShop全系列版本的遠程代碼執行漏洞》預警。
從2018年的6月13日首次攔截后,知道創宇404實驗室多個團隊對這個利用ECShop 0day攻擊事件進行持續的監控分析,從下文的分析結果可以看出一個0day漏洞在實際攻擊中的各個階段的“墮落”過程。
漏洞分析
該漏洞影響ECShop 2.x和3.x版本,是一個典型的“二次漏洞”,通過user.php
文件中display()
函數的模板變量可控,從而造成SQL注入漏洞,而後又通過SQL注入漏洞將惡意代碼注入到危險函數eval
中,從而實現了任意代碼執行。
值得一提的是攻擊者利用的payload只適用於ECShop 2.x版本導致有部分安全分析者認為該漏洞不影響ECShop 3.x,這個是因為在3.x的版本里有引入防注入攻擊的安全代碼,通過我們分析發現該防禦代碼完全可以繞過實現對ECShop 3.x的攻擊(詳見下文分析)。
註:以下代碼分析基於ECShop 2.7.3
SQL 注入漏洞
首先看到ecshop/user.php
elseif ($action == 'login') { if (empty($back_act)) { if (empty($back_act) && isset($GLOBALS['_SERVER']['HTTP_REFERER'])) { $back_act = strpos($GLOBALS['_SERVER']['HTTP_REFERER'], 'user.php') ? './index.php' : $GLOBALS['_SERVER']['HTTP_REFERER']; } else { $back_act = 'user.php'; } } $captcha = intval($_CFG['captcha']); if (($captcha & CAPTCHA_LOGIN) && (!($captcha & CAPTCHA_LOGIN_FAIL) || (($captcha & CAPTCHA_LOGIN_FAIL) && $_SESSION['login_fail'] > 2)) && gd_version() > 0) { $GLOBALS['smarty']->assign('enabled_captcha', 1); $GLOBALS['smarty']->assign('rand', mt_rand()); } $smarty->assign('back_act', $back_act); $smarty->display('user_passport.dwt'); }
可以看到$back_act
是從HTTP_REFERER
獲取到的,HTTP_REFERER
是外部可控的,這也是萬惡的根源。
接着將back_act
變量傳遞給assign
函數,跟進ecshop/includes/cls_template.php
/** * 註冊變量 * * @access public * @param mix $tpl_var * @param mix $value * * @return void */ function assign($tpl_var, $value = '') { if (is_array($tpl_var)) { foreach ($tpl_var AS $key => $val) { if ($key != '') { $this->_var[$key] = $val; } } } else { if ($tpl_var != '') { $this->_var[$tpl_var] = $value; } } }
可以從註釋了解這個函數的功能,是註冊模板變量,也就是$back_act
變成了$this->_var[$back_act]=$back_act
,而後調用display
函數
function display($filename, $cache_id = '') { $this->_seterror++; error_reporting(E_ALL ^ E_NOTICE); $this->_checkfile = false; $out = $this->fetch($filename, $cache_id); if (strpos($out, $this->_echash) !== false) { $k = explode($this->_echash, $out); foreach ($k AS $key => $val) { if (($key % 2) == 1) { $k[$key] = $this->insert_mod($val); } } $out = implode('', $k); } error_reporting($this->_errorlevel); $this->_seterror--; echo $out; }
從user.php
調用display
函數,傳遞進來的$filename
是user_passport.dwt
,從函數來看,首先會調用$this->fetch
來處理user_passport.dwt
模板文件,fetch
函數中會調用$this->make_compiled
來編譯模板。user_passport.dwt
其中一段如下:
<td> </td> <td align="left"> <input type="hidden" name="act" value="act_login" /> <input type="hidden" name="back_act" value="{$back_act}" /> <input type="submit" name="submit" value="" class="us_Submit" /> </td>
make_compiled
會將模板中的變量解析,也就是在這個時候將上面assign
中註冊到的變量$back_act
傳遞進去了,解析完變量之後返回到display
函數中。此時$out
是解析變量后的html內容,判斷$this->_echash
是否在$out
中,若在,使用$this->_echash
來分割內容,得到$k
然後交給insert_mod
處理。
转载请注明:IAMCOOL » ECShop 0day 的墮落之路