ECShop全系列版本远程代码执行
2018-09-27 本文已影响26人
WillDST
该漏洞产生的根本原因在于ECShop系统的user.php文件中,display函数的模板变量可控,导致注入,配合注入可达到远程代码执行的效果。
1、SQL注入
传入的HTTP_REFERER会保存到$back_act变量中,在assign函数中进行变型,在通过display的函数调用将之前的referer信息写入模版中 将模版通过echash进行分段存入数组数组的奇数下标将调用insert_mod通过合适的referer参数使其调用insert_ads函数进行报错注入。 当$back_act为空、传入的head中存在referer参数时,将会判断referer的链接中是否存在user.php,如果存在$back_act为./index.php、如果不存在$back_act为当前传入的referer值。 Back_act在收到referer参数之后将会把该参数传入assign函数中。Assign函数在includes/cls_template.php文件中被声明,是一个注册模版变量的函数该函数中将$back_act变量的值保存入模版变量的数组中。 Display为显示页面的函数,同样声明在includes/cls_template.php文件中。$out = $this->fetch($filename, $cache_id); 对传入的filename对应的模版文件进行处理此处的通过user.php调用的display函数,filename为user_passport.dwt在user_passport.dwt文件中包含了之前带有referer信息的$back_act参数 在fetch函数中将会调用make_compiled函数make_compiled函数的返回值source为被编译后的文件内容,该内容在fetch中同样作为返回值out进行返回。 通过out返回html,通过_echash进行字符串分割,选中_echash右键在工程中查询声明处,双击查询到的声明处可见在类cls_template中定义了_echash的值,是一个常量Explode将会根据分割字符将字符串保存在数组k中 当k的键值为奇数时将会将val的值放入insert_mod函数中进行处理进入该函数,函数第一行通过|作为分隔符,将name进行分割,并将分割后的字符串通过list批量赋值给fun和para,如果此处分割后有三个字符串,将赋值前两个给list。然后将para进行反序列化处理后,返回php值在fun的值中加上insert前缀,然后调用该函数insert_member_info可见在调用该函数的时候,函数名前缀insert不可控,后面部分可控;参数完全可控。 Insert为前缀的函数在Lib_insert.php文件中,一共有8个。其中带有参数,并且调用了数据库的函数只有两个,分别为ads与bought_notes在这两个函数中都拼接了传入的可控数据。 在进行SQL注入语句构建之前,先来看一下之前的out变量,该变量之前大小为10044字节,但是在调试器中只能看前1024个字节现在添加语句file_put_contents将该变量输出到文件out.php查看back_act的可控变量在115行在该php文件中向下查询echash的值554fcae493e564ee0dc75bdf2ebf94ca,下一个数据在180行,是原来数组中序号为3的数据。也就是说在可控数据上添加分隔符之后,作为数组的奇数下标,将会调用insert_mod,从而调用可控函数 在数据库查询语句中进行拼接的字符有两个分别为$arr['id']与$arr['num’]在limit之后的注入可使用procedureanalyse执行报错注入。2、代码执行
在SQL查询结束之后,将会把查询结果传入res中, 当返回结果中的$row[‘position_id‘]与传入的参数$arr[‘id’]相同时,会将之前SQL查询结果中的position_style传递给$position_style。 之后$position_style会拼接'str:'传入fetch函数 需要构造的条件:$row[‘position_id‘]==$arr[‘id’] 在此调用fetch方法,该方法是处理模版文件的方法, 之前在user.php中通过display调用过fetch方法,那时传入的参数是user_passport.dwt, 而在此处传入的参数是$position_style, 因为之前拼接'str:'了,所以strncmp($filename,'str:', 4) == 0为真, 然后将去除了str:的字符串传入危险函数$this->_eval,代码执行触发点, 参数在传递之前要经过fetch_str方法的处理。 第一个正则会匹配一些关键字,然后置空 关键字主要分为三个组,第一组为该特征字符串不是以a-zA-Z0-9_这些字符开头、{1,1}第一个字符至少匹配一次并且最多匹配一次。 第二组为不存在关键字copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo 第三组为关键字后不跟空格或者左括号,例如:@copy(将会被过滤。 然后跳过if的判断体,查看最后在返回时的正则表达式。 return preg_replace("/{([^\}\{\n]*)}/e", "\$this->select('\\1');", $source);这个正则是将捕获到group 1交于$this-select()函数处理。 在cls_template类中找到select函数,该函数主要的作用是处理{}标签中的数据。 当传入的字符是以$开头表示一个变量时,将会为其加上php标签。 在返回之前,将会去调用get_val函数来对输入的字符串进行处理 get_val函数是处理smarty标签中的变量标签。 为了尽可能在该函数中少执行代码,那么可选择走else中的$p = $this->make_var($val);这条路 并且保持moddb为空,那么就不会进入下面的if语句中。 在该函数中只执行一句代码,该句代码调用了make_var 该函数主要是用来处理去掉了$的字符 在该函数中引导程序,$p = '$this->_var[\'' . $val . '\']';这句代码会将传入的所有变量传递到方括号+单引号中,然后将返回值p送到函数_eval函数中。 在该函数中,先打开一个输出缓冲 然后闭合页面的之前的php标签,开始执行传入的参数 将返回的字符串保存在content中,进行返回。 执行额外的代码使用分号来执行两条语句,那么就需要先进行引号和方括号闭合,然后通过//将之后的代码进行注释,因为之后还有程序自行添加的引号和方括号;不然将会导致在eval中是不合法的php代码。 $val为abc'];echo phpinfo();// 从select函数进入get_var的条件是第一个字符是$,所以payload变成了$abc'];echo phpinfo();// 而要进入到select,需要被捕获,payload变成了{$abc'];echo phpinfo();//}, 这里因为payload的是phpinfo(),这里会被fetch_str函数的第一个正则匹配到,需要变换一下,通过/**/来分割字符,所以payload变为{$abc'];echo phpinfo/**/();//} 由于需要控制输出,那么就不允许默认查询结果输出,可用union select 来控制查询的结果, 根据之前的流程,$row['position_id']和$arr['id']要相等, 根据查询语句的排列 $row['position_id']是第二列的结果,$position_style是第九列的结果。 由于这里使用的是union select 所以不能在limit之后执行,可使用/**/将id与num中间的代码注释掉。 $arr[‘id’]传入' /*, 引号空格斜杠星号 $arr['num']传入*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7B24616161275D3B73797374656D2F2A2A2F282764697227293B2F2F7D,10--, 0x27202f2a是' /*的16进制值,也就是$row['position_id']的值,0x7B24616161275D3B73797374656D2F2A2A2F282764697227293B2F2F7D是上面构造的php代码的16进制值,也就是$position_style。 $out = serialize(array('*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7B24616161275D3B73797374656D2F2A2A2F282764697227293B2F2F7D,10--' ,'id'=>"' /*")); 在结尾加上分隔符 因为奇数的数组下标将会执行insert函数,如果不加一个分割符的话,将会导致后续进入insert函数的参数不对,导致页面出错。3、测试代码
4、参考链接
http://ringk3y.com/2018/08/31/ecshop2-x%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C/
https://paper.seebug.org/695/
https://mp.weixin.qq.com/s/fXw5VVXQsxv1N-lgWTIHzA