### 简要描述: KingCms最新版目录遍历及任意文件读取漏洞(无需截断) ### 详细说明: 朋友的公司想购买kingcms的授权,让我帮忙看下。发现kingcms很长一段时间没更新了,憋了一段时间放出了最新版的k9(2014-12-13更新),官网下下来学习一下。 在wooyun上看到了几个漏洞,如: [WooYun: kingcms最新版sql注入漏洞](http://www.wooyun.org/bugs/wooyun-2013-043520) 问题出在这里:/api/conn.php 先首需要说明的是,这里的目录遍历与文件读取并不是因为服务器配置不当等引起的,而是该cms的某些函数没过过滤+使用不当引起的。 0x00:先来看看如何目录遍历的。 ``` 无关代码 $get=$_GET; if(empty($get['jsoncallback'])) exit('非法提交!'); $jsoncallback=$_GET['jsoncallback']; if(empty($_GET['USERID'])) exit($jsoncallback.'('.json_encode(array('error'=>"用户ID不能为空,请求失败!")).')'); $str=new str; $db=new db; $file=new file; if(empty($get['SIGN'])) exit($jsoncallback.'('.json_encode(array('error'=>'丢失密文,禁止解析!')).')'); //验证权限 $sign=$get['SIGN']; unset($get['SIGN'],$get['_'],$get['jsoncallback']); $arr=array(); foreach($get as $key => $val){ $arr[$key]=$key.'='.urlencode($val); } ksort($arr); $param=implode('&',$arr); $sign1=md5($param.kc_config('system.salt')); if($sign!=$sign1)...
### 简要描述: KingCms最新版目录遍历及任意文件读取漏洞(无需截断) ### 详细说明: 朋友的公司想购买kingcms的授权,让我帮忙看下。发现kingcms很长一段时间没更新了,憋了一段时间放出了最新版的k9(2014-12-13更新),官网下下来学习一下。 在wooyun上看到了几个漏洞,如: [WooYun: kingcms最新版sql注入漏洞](http://www.wooyun.org/bugs/wooyun-2013-043520) 问题出在这里:/api/conn.php 先首需要说明的是,这里的目录遍历与文件读取并不是因为服务器配置不当等引起的,而是该cms的某些函数没过过滤+使用不当引起的。 0x00:先来看看如何目录遍历的。 ``` 无关代码 $get=$_GET; if(empty($get['jsoncallback'])) exit('非法提交!'); $jsoncallback=$_GET['jsoncallback']; if(empty($_GET['USERID'])) exit($jsoncallback.'('.json_encode(array('error'=>"用户ID不能为空,请求失败!")).')'); $str=new str; $db=new db; $file=new file; if(empty($get['SIGN'])) exit($jsoncallback.'('.json_encode(array('error'=>'丢失密文,禁止解析!')).')'); //验证权限 $sign=$get['SIGN']; unset($get['SIGN'],$get['_'],$get['jsoncallback']); $arr=array(); foreach($get as $key => $val){ $arr[$key]=$key.'='.urlencode($val); } ksort($arr); $param=implode('&',$arr); $sign1=md5($param.kc_config('system.salt')); if($sign!=$sign1) exit($jsoncallback.'('.json_encode(array('error'=>'审核失败,数据不一致!')).')'); $arr=array(); $get=array_map('base64_decode',$get); foreach($get as $key=>$val){ if(substr($key,0,8)=='data_one'){ $arr[$key]=$db->get_one($val); }elseif(substr($key,0,4)=='data'){ $arr[$key]=$db->get($val); }elseif(substr($key,0,5)=='count'){ $res=$db->get($val); $arr[$key]=empty($res[0]['c']) ? 0 : $res[0]['c']; }elseif(substr($key,0,5)=='newid'){ list($table,$id)=explode('|',$val,2); $arr[$key]=$db->newid($table,'',$id); }elseif(substr($key,0,6)=='getdir'){ list($path,$filetype)=explode('|',$val,2); if(empty($filetype)) $filetype='*'; $arr[$key]=$file->getDir($path,$filetype); }elseif(substr($key,0,7)=='getfile'){ $arr[$key]=$file->get($val); }elseif(substr($key,0,6)=='config'){ $arr[$key]=kc_config($val); }elseif(substr($key,0,6)=='isfile'){ $arr[$key]=is_file(ROOT.$val)?1:0; } } //判断url值,如果有这个值就有分页,count,rn和pid不能为空 if(!empty($get['url'])){ $url=$get['url']; $rows=$arr['count']; $pid=$get['pid']; $rn=$get['rn']; $arr['pagelist']=$str->pagelist($url, $rows, $pid, $rn,'<em>[count]</em>[standard][next]'); } ``` 先来看看怎么绕过上面代码中的权限验证,也即要使得$sign1与$sign相等,其中$sing是用户输入的,$sign1的计算方法如下: ``` $arr=array(); foreach($get as $key => $val){ $arr[$key]=$key.'='.urlencode($val); } ksort($arr); $param=implode('&',$arr); $sign1=md5($param.kc_config('system.salt')); ``` 计算过程中所用到的参数只有kc_config('system.salt')不是用户输入的,但是在kingcms中,这个参数全都是空的,也就是说$sing1完全由用户的输入参数按上述算法计算得来,因此,这里可以轻松绕过。然后执行到这句$arr[$key]=$file->getDir($path,$filetype); 跟进 ``` function getDir($path='',$type='*',$ignore=array('.','..','.svn')){ if (!is_dir(ROOT.$path)) { return array(); } $dirs = $files = array(); $handle=opendir(ROOT.$path); if($handle){ while (false !== ($file=@readdir($handle))){ $file=$this->encode($file); if(!in_array($file,$ignore)){ if(is_dir(ROOT.$path.$file)){//如果是dir if($type=='*'||$type=='dir') $dirs[$path.$file]=$file; }else{//文件 if($type=='*'||$type=='file'||preg_match("/^.+\.({$type})$/i",$file)) $files[$path.$file]=$file; } } } closedir($handle); //sort($files); //sort($dirs); $union=array_merge($dirs,$files); return $union; }else{ kc_tip('找不到指定的目录<br/>'.$path); } } ``` 直接把目录列出来了。路径是从根文件开始的,这里输入路径时,要base64 encode一下。成功列目录,如图 [<img src="https://images.seebug.org/upload/201503/222119317290bf86e677124981f735ea90c0d5cc.jpg" alt="列目录成功副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201503/222119317290bf86e677124981f735ea90c0d5cc.jpg) 0x01:再来看看如何实现任意文件读取的。 代码与上面第一段相同,这里就不贴出来了。看到这句$arr[$key]=$file->get($val); 跟进 ``` public function get($filename){ $s=''; $filename=$this->encode($filename,1); if(empty($GLOBALS['file_get_contents_array'])) $GLOBALS['file_get_contents_array']=array(); if(is_file(ROOT.$filename)){//如果存在则读取 $s=''; $fh = fopen(ROOT.$filename,"r"); while (!feof($fh)) { $s.=fgets($fh); } fclose($fh); } return $s; } ``` 直接把文件读出并返回了,路径是从根文件开始的,这里输入文件名及路径时,要base64 encode一下。文件成功读取,如图 [<img src="https://images.seebug.org/upload/201503/222120043cc4f1104ae267dee1733c31ed1979ac.jpg" alt="读取过程副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201503/222120043cc4f1104ae267dee1733c31ed1979ac.jpg) ### 漏洞证明: 见 详细说明