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

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

缺陷编号:wooyun-2014-069606

漏洞标题:代码审计系列10: 快范CMS Persistent XSS 直取管理员首级

相关厂商:kuaifan.net

漏洞作者: LaiX

提交时间:2014-07-25 23:00

修复时间:2014-10-20 23:02

公开时间:2014-10-20 23:02

漏洞类型:xss跨站脚本攻击

危害等级:中

自评Rank:10

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-07-25: 细节已通知厂商并且等待厂商处理中
2014-07-30: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2014-09-23: 细节向核心白帽子及相关领域专家公开
2014-10-03: 细节向普通白帽子公开
2014-10-13: 细节向实习白帽子公开
2014-10-20: 细节向公众公开

简要描述:

纯代码审计查找XSS漏洞 。快范CMS Persistent XSS 直取管理员首级

详细说明:

首先来看看后台登录文件的源码.
\kuaifan\module\admin\login.module.php

<?php
/*
* 登录后台
* ============================================================================
* 版权所有: 快范网络,并保留所有权利。
* 网站地址: http://www.kuaifan.net;
* ----------------------------------------------------------------------------
* 这不是一个自由软件!您只能在不用于商业目的的前提下对程序代码进行修改和
* 使用;不允许对程序代码以任何形式任何目的的再发布。
* ============================================================================
*/
if(!defined('IN_KUAIFAN')) exit('Access Denied!');
if (!empty($_POST['dosubmit'])){
$wheresql = " WHERE name ='{$_POST['username']}' and pass ='".md5s($_POST['userpass'])."'";
$val = $db -> getone("select * from ".table('guanliyuan').$wheresql." LIMIT 1");
if (!empty($val)){
admin_log("“ID{$val['id']}[{$val['rank']}]{$val['name']}”登录成功!", $_POST['username'], 1);
$setsql = "lasttime=nowtime,lastip=nowip,nowtime='".time()."',nowip='".$online_ip."',logins=logins+1";
if (strlen($val['allow'])!=25) {$val['allow'] = generate_password(25);$setsql.= ",allow='".$val['allow']."'";}
$db -> query("update ".table('guanliyuan')." set ".$setsql." WHERE id='{$val['id']}'");
$links[0]['title'] = '点击进入';
$links[0]['href'] = get_link("c,allow")."&amp;c=index&amp;allow=".$val['allow'];
$links[1]['title'] = '返回网站首页';
$links[1]['href'] = kf_url('index');
$ip_area = kf_class::run_sys_class('ip_area');
$ip_city = $ip_area->get($val['nowip']);
if ($ip_city == 'Unknown'){
$ip_city_arr = $ip_area->getcitybyapi($val['nowip']);
$ip_city = $ip_city_arr['city'];
}
$text = "登录成功!<br/>-------------<br/>上次登录:".date('Y-m-d H:i:s',$val['nowtime'])."<br/>上次IP:".$val['nowip'];
$text.= $ip_city?"({$ip_city})":"";
if (!$val['nowtime']) $text = "登录成功!<br/>-------------<br/>上次登录:无(第一次登陆)";
showmsg("系统提醒", $text, $links, $links[0]['href']);
//header("Location:".get_link("c,allow","&")."&c=index&allow=".$val['allow']); exit;
}else{
admin_log("登录失败!", $_POST['username']);
$links[0]['title'] = '登录后台';
$links[0]['href'] = get_link("c,allow")."&amp;c=login";
$links[1]['title'] = '返回网站首页';
$links[1]['href'] = kf_url('index');
showmsg("登录失败", "帐号或者密码错误!"."<br>".$wheresql, $links, $links[0]['href']);

}
}
?>


$db 这个库是没有问题的,提交单引号会被加斜杠过滤。
这时我们发一个比较显眼的函数,admin_log()
跟踪一下发现:
\include\libs\functions\admin.func.php

//写入管理员操作日志
function admin_log($str, $user, $log_type=0, $log_site=1)
{
global $db, $timestamp,$online_ip,$_CFG;
if ($_CFG['admin_islog']=='1'){
$sql = "INSERT INTO ".table('guanliyuan_rizhi')." (name, time, body, ip, type, site) VALUES ('$user', '$timestamp', '".htmlspecialchars($str,ENT_QUOTES)."','$online_ip','".intval($log_type)."', '".intval($log_site)."')";
return $db->query($sql);
}
}


看了一下SQL语句,发现 htmlspecialchars 只停留在了系统自带的提示字符上 (admin_log 函数的第一个参数),而对用户所控制的 $user 变量 (第二个参数)并没有过滤。难道是当时程序员喝醉酒了?
那么这个log体现在哪里呢
我们从上面的SQL可以看出来,数据是进入了 guanliyuan_rizhi 这个表。
接下来查找一下哪些文件读取了guanliyuan_rizhi这个表。
找到了在下面这个文件中读取了 guanliyuan_rizhi 这个表的地方。
\include\libs\plugins\function.kuaifan_adminrizhi.php

<?php
function smarty_function_kuaifan_adminrizhi($params, &$smarty)
{
global $db;
$arr=explode(',',$params['set']);
foreach($arr as $str)
{
$a=explode(':',$str);
switch ($a[0])
{
case "列表名":
$aset['listname'] = $a[1];
break;
case "ID列表":
$aset['listname_id'] = $a[1];
break;
case "显示数目":
$aset['row'] = $a[1];
break;
case "标题长度":
$aset['titlelen'] = $a[1];
break;
case "开始位置":
$aset['start'] = $a[1];
break;
case "填补字符":
$aset['dot'] = $a[1];
break;
case "分页显示":
$aset['paged'] = $a[1];
break;
case "分页名":
$aset['pagename'] = $a[1];
break;
case "分页变量名":
$aset['page_name'] = $a[1];
break;
case "搜索变量名":
$aset['keyname'] = $_REQUEST[$a[1]];
$aset['key_name'] = $a[1];
break;
case "分类":
$aset['type'] = $a[1];
break;
case "管理员":
$aset['adminname'] = $a[1];
break;
}
}
if (is_array($aset)) $aset=array_map("get_smarty_request",$aset);
$aset['listname']=isset($aset['listname'])?$aset['listname']:"list";
$aset['row']=isset($aset['row'])?intval($aset['row']):10;
$aset['start']=isset($aset['start'])?intval($aset['start']):0;
$aset['titlelen']=isset($aset['titlelen'])?intval($aset['titlelen']):15;
$aset['pagename']=isset($aset['pagename'])?$aset['pagename']:'page';
$aset['key_name']=isset($aset['key_name'])?$aset['key_name']:'key';
$orderbysql=" ORDER BY time DESC,id DESC";
$wheresql = '';
$wheresql.=isset($aset['type'])?' AND type=\''.$aset['type'].'\'':'';
$wheresql.=isset($aset['adminname'])?' AND name=\''.$aset['adminname'].'\'':'';
if (!empty($aset['keyname'])){
$keyname=trim($aset['keyname']);
$wheresql.=" AND (`name` like '%{$keyname}%' or `body` like '%{$keyname}%')";
$_GET[$aset['key_name']] = $keyname;
}
if (!empty($wheresql)){
$wheresql=" WHERE ".ltrim(ltrim($wheresql),'AND');
}
if (isset($aset['paged']))
{
kf_class::run_sys_class('page','',0);
$total_sql="SELECT COUNT(*) AS num FROM ".table('guanliyuan_rizhi').$wheresql;
$total_count=$db->get_total($total_sql);
$pagelist = new page(array('total'=>$total_count,'perpage'=>$aset['row'],'getarray'=>$_GET,'page_name'=>$aset['page_name']));
$currenpage=$pagelist->nowindex;
$aset['start']=($currenpage-1)*$aset['row'];
$smarty->assign($aset['pagename'],$pagelist->show('wap'));
$pagearr = array(
'total'=>$total_count, //总数
'perpage'=>$aset['row'], //每页显示
'nowpage'=>$currenpage, //当前页
'totalpage'=>$pagelist->totalpage, //总页数
);
$smarty->assign($aset['pagename'].'_info',$pagearr);
}
$limit=" LIMIT ".abs($aset['start']).','.$aset['row'];
$result = $db->query("SELECT * FROM ".table('guanliyuan_rizhi')." ".$wheresql.$orderbysql.$limit);
$list= array();
$__n= 1;$_timearrid = '';
while($row = $db->fetch_array($result))
{
$_timearrid.= $row['id'].',';
$row['_n']=$__n+($currenpage*$aset['row'])-$aset['row'];
$row['body_']=$row['body'];
$row['body']=cut_str($row['body'],$aset['titlelen'],0,$aset['dot']);
$__n ++;
$list[] = $row;
}
if ($aset['listname_id']) $smarty->assign($aset['listname_id'],rtrim($_timearrid,','));
$smarty->assign($aset['listname'],$list);
}
?>


OK,
再来看看,哪个文件引用了这个这个函数。
经过查找,发现了一个临时文件引用了这个函数。

<?php /* Smarty version Smarty-3.1.13, created on 2014-07-24 20:48:55
compiled from "templates\default\admin\adminrizhi\index.tpl" */ ?>
<?php /*%%SmartyHeaderCode:20853d100b7ed9db9-95930666%%*/if(!defined('SMARTY_DIR')) exit('no direct access allowed');
$_valid = $_smarty_tpl->decodeProperties(array (
'file_dependency' =>
array (
'141c5823698d5bc5c081d8dc786047b3dc8d4dc4' =>
array (
0 => 'templates\\default\\admin\\adminrizhi\\index.tpl',
1 => 1387965391,
2 => 'file',
),
),
'nocache_hash' => '20853d100b7ed9db9-95930666',
'function' =>
array (
),
'variables' =>
array (
'TIME2' => 0,
'lists' => 0,
'list' => 0,
'pagelist' => 0,
'idlist' => 0,
),
'has_nocache_code' => false,
'version' => 'Smarty-3.1.13',
'unifunc' => 'content_53d100b8004fc9_89096927',
),false); /*/%%SmartyHeaderCode%%*/?>
<?php if ($_valid && !is_callable('content_53d100b8004fc9_89096927')) {function content_53d100b8004fc9_89096927($_smarty_tpl) {?><?php if (!is_callable('smarty_function_kuaifan_adminrizhi')) include 'D:\\xampp\\htdocs\\include\\libs\\plugins\\function.kuaifan_adminrizhi.php';
if (!is_callable('smarty_modifier_date_format')) include 'D:\\xampp\\htdocs\\include\\libs\\plugins\\modifier.date_format.php';
?><?php echo $_smarty_tpl->getSubTemplate ("common/header.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, null, null, array('title_top'=>"1",'title'=>"后台日志"), 0);?>
[管理员操作日志]<br/>
-------------<br/>
<?php if ($_GET['name']){?>
已选[<?php echo $_GET['name'];?>
]日志.<a href="<?php echo get_link('pp|name');?>
">取消选择</a><br/>
<?php }?>
<?php ob_start();?><?php echo str_replace(':','\:',get_link('pp|key'));?>
<?php $_tmp1=ob_get_clean();?><?php echo format_form(array('set'=>"头|地址:'".$_tmp1."'",'notvs'=>"1"),$_smarty_tpl);?>
<?php echo format_form(array('set'=>"输入框|名称:key".((string)$_smarty_tpl->tpl_vars['TIME2']->value).",值:'".((string)$_REQUEST['key'])."',宽:12"),$_smarty_tpl);?>
<?php ob_start();?><?php echo get_link('pp|key');?>
<?php $_tmp2=ob_get_clean();?><?php echo format_kuaifan(array('vs'=>"1",'set'=>"
<anchor>搜索
<go href='".$_tmp2."' method='post' accept-charset='utf-8'>
<postfield name='key' value='"."$"."(key".((string)$_smarty_tpl->tpl_vars['TIME2']->value).")'/>
<postfield name='dosubmit' value='1'/>
</go> </anchor>
"),$_smarty_tpl);?>
<?php echo format_form(array('set'=>"按钮|名称:dosubmit,值:搜索",'notvs'=>"1"),$_smarty_tpl);?>
<?php echo format_form(array('set'=>"尾",'notvs'=>"1"),$_smarty_tpl);?>
<br/>
<?php echo smarty_function_kuaifan_adminrizhi(array('set'=>"列表名:lists,ID列表:idlist,管理员:GET[name],显示数目:20,搜索变量名:key,标题长度:15,填补字符:...,分页显示:1,分页名:pagelist,分页变量名:pp"),$_smarty_tpl);?>
<?php $_smarty_tpl->tpl_vars['list'] = new Smarty_Variable; $_smarty_tpl->tpl_vars['list']->_loop = false;
$_from = $_smarty_tpl->tpl_vars['lists']->value; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array');}
foreach ($_from as $_smarty_tpl->tpl_vars['list']->key => $_smarty_tpl->tpl_vars['list']->value){
$_smarty_tpl->tpl_vars['list']->_loop = true;
?>
<?php echo $_smarty_tpl->tpl_vars['list']->value['name'];?>
(<?php echo $_smarty_tpl->tpl_vars['list']->value['ip'];?>
):<br/>
<?php echo $_smarty_tpl->tpl_vars['list']->value['body_'];?>
(<?php echo smarty_modifier_date_format($_smarty_tpl->tpl_vars['list']->value['time'],"%Y-%m-%d %H:%M:%S"); ?>
)<br/>-----<br/>
<?php }
if (!$_smarty_tpl->tpl_vars['list']->_loop) {
?>
没有任何日志。<br/>
<?php } ?>
<?php echo $_smarty_tpl->tpl_vars['pagelist']->value;?>
<?php if ($_smarty_tpl->tpl_vars['idlist']->value){?>
<br/>*<a href="<?php echo get_link('del');?>
&amp;del=<?php echo $_smarty_tpl->tpl_vars['idlist']->value;?>
">删除本页日志</a>
<?php }?>
<br/>*<a href="<?php echo get_link('del');?>
&amp;del=all">删除全部日志x</a>
<br/>
<?php echo $_smarty_tpl->getSubTemplate ("admin/footer.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, null, null, array(), 0);?>
<?php echo $_smarty_tpl->getSubTemplate ("common/footer.tpl", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, null, null, array(), 0);?>
<?php }} ?>


可以看见,输出的地方也没有经过过滤,看源码不难知道这是一个后台的功能。应该是属于查看日志类的东西。

漏洞证明:

1.png


2.png


3.png


4.png

修复方案:

注意一下吧

版权声明:转载请注明来源 LaiX@乌云


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2014-10-20 23:02

厂商回复:

最新状态:

2014-07-30:正在更新

2014-08-01:20140730更新包已经修复