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
ecshop/user.php
可以看到 $back_act 是从 HTTP_REFERER 获取到的, HTTP_REFERER 是外部可控的,这也是万恶的根源。
接着将 back_act 变量传递给 assign 函数,跟进 ecshop/includes/cls_template.php
可以从注释了解这个函数的功能,是注册模板变量,也就是$back_act
变成了$this->_var[$back_act]=$back_act
,而后调用display
函数
从user.php
调用display
函数,传递进来的$filename
是user_passport.dwt
,从函数来看,首先会调用$this->fetch
来处理user_passport.dwt
模板文件,fetch
函数中会调用$this->make_compiled
来编译模板。user_passport.dwt
其中一段如下:
make_compiled
会将模板中的变量解析,也就是在这个时候将上面assign
中注册到的变量$back_act
传递进去了,解析完变量之后返回到display
函数中。此时$out
是解析变量后的html内容,判断$this->_echash
是否在$out
中,若在,使用$this->_echash
来分割内容,得到$k
然后交给insert_mod
处理。
由于_echash
是默认的,不是随机生成的,所以$val
内容可随意控制。跟进$this->insert_mod
$val
传递进来,先用|
分割,得到$fun
和$para
,$para
进行反序列操作,$fun
和insert_
拼接,最后动态调用$fun($para)
,函数名部分可控,参数完全可控。接下来就是寻找以insert_
开头的可利用的函数了,在ecshop/includes/lib_insert.php
有一个insert_ads
函数,正好满足要求。看下insert_ads
$arr
是可控的,并且会拼接到SQL语句中,这就造成了SQL注入漏洞。
根据上面的流程,可以构造出如下形式的payload
实际可利用payload
insert_ads
函数可以看到在SQL查询结束之后会调用模板类的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
因为之前拼接'str:'
了,所以strncmp($filename,'str:', 4) == 0
为真,然后会调用危险函数$this->_eval
,这就是最终触发漏洞的点。但是参数在传递之前要经过fetch_str
方法的处理,跟进
第一个正则会匹配一些关键字,然后置空,主要看下最后一个正则
这个正则是将捕获到的值交于$this-select()
函数处理。例如,$source
的值是xxx{$abc}xxx
,正则捕获到的group 1 就是$abc
,然后就会调用$this-select("$abc")
。
跟进select
函数
当传入的变量的第一个字符是$
,会返回由 php 标签包含变量的字符串,最终返回到_eval()
危险函数内,执行。在返回之前,还调用了$this->get_var
处理,跟进 et_var