发布于2022年11月4日2年前 该漏洞产生的根本原因在于ECShop系统的user.php文件中,display函数的模板变量可控,导致注入,配合注入可达到远程代码执行的效果。使得攻击者无需登录等操作,直接可以获得服务器的权限。首先从user.php文件入手,代码中可以看到,系统读取HTTP_REFERER传递过来的内容赋值给$back_act变量。接着以$back_act的值为参数,调用assign方法。/* 用户登录界面 */ 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'); }assign方法的作用是把可控变量传递给模版函数,紧接着再通过display方法展示到页面上。文件:/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; } } } /** * 显示页面函数 * * @access public * @param string $filename * @param sting $cache_id * * @return void */ 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; }接下来跟进display内部的insert_mod方法。文件:/includes/cls_template.phpfunction insert_mod($name) // 处理动态内容 { list($fun, $para) = explode('|', $name); $para = unserialize($para); $fun = 'insert_' . $fun; return $fun($para); }insert_mod方法返回了一个动态函数调用,该函数名和参数均可控,根据攻击者的利用方法,我们可以得知调用的函数名为insert_ads,接下来跟进这一方法。文件:/includes/lib_insert.php/** * 调用指定的广告位的广告 * * @access public * @param integer $id 广告位ID * @param integer $num 广告数量 * @return string */ function insert_ads($arr) { static $static_res = NULL; $time = gmtime(); if (!empty($arr['num']) && $arr['num'] != 1) { $sql = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, ' . 'p.ad_height, p.position_style, RAND() AS rnd ' . 'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '. 'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' . "WHERE enabled = 1 AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' ". "AND a.position_id = '" . $arr['id'] . "' " . 'ORDER BY rnd LIMIT ' . $arr['num']; $res = $GLOBALS['db']->GetAll($sql); } ...//ignore $position_style = 'str:' . $position_style; $need_cache = $GLOBALS['smarty']->caching; $GLOBALS['smarty']->caching = false; $GLOBALS['smarty']->assign('ads', $ads); $val = $GLOBALS['smarty']->fetch($position_style); $GLOBALS['smarty']->caching = $need_cache; return $val; }接着,程序调用了fetch方法,参数由$row['position_style']变量赋值,这一变量同样为外部可控输入点。文件:/includes/lib_insert.php$position_style = 'str:' . $position_style; $need_cache = $GLOBALS['smarty']->caching; $GLOBALS['smarty']->caching = false; $GLOBALS['smarty']->assign('ads', $ads); $val = $GLOBALS['smarty']->fetch($position_style); $GLOBALS['smarty']->caching = $need_cache; return $val;这里fetch函数调用了危险函数,这就是最终触发漏洞的点。但是参数在传递之前要经过fetch_str方法的处理。文件:/includes/cls_template.phpfunction fetch($filename, $cache_id = '') { if (!$this->_seterror) { error_reporting(E_ALL ^ E_NOTICE); } $this->_seterror++; if (strncmp($filename,'str:', 4) == 0) { $out = $this->_eval($this->fetch_str(substr($filename, 4))); //漏洞点 } ...//ignore }最终输入点依次经过fetch_str、select、get_val,最终传入make_var方法。文件:/includes/cls_template.phpfunction make_var($val) { if (strrpos($val, '.') === false) { if (isset($this->_var[$val]) && isset($this->_patchstack[$val])) { $val = $this->_patchstack[$val]; } $p = '$this->_var[\'' . $val . '\']'; } else { $t = explode('.', $val); $_var_name = array_shift($t); if (isset($this->_var[$_var_name]) && isset($this->_patchstack[$_var_name])) { $_var_name = $this->_patchstack[$_var_name]; } if ($_var_name == 'smarty') { $p = $this->_compile_smarty_ref($t); } else { $p = '$this->_var[\'' . $_var_name . '\']'; } foreach ($t AS $val) { $p.= '[\'' . $val . '\']'; } } return $p; }最终传递到eval的字符串为:到此,漏洞原理分析完成,攻击者的恶意代码执行成功。 版权属于:逍遥子大表哥本文链接:https://blog.bbskali.cn/371.html按照知识共享署名-非商业性使用 4.0 国际协议进行许可,转载引用文章应遵循相同协议。
创建帐户或登录后发表意见