一、SQL注入原因

SQL漏洞出现的原因大家都应该知道了,还是哪一句熟悉的话:用户输入的数据被SQL解释器执行。

SQL注入的原因和分类都是很简单的,难的是对可能出现SQL注入的地方进行细致拼接测试。


二、SQL注入漏洞分类

2.1 数字型注入

数字型注入是最初级的注入,常出现在ASP、PHP等弱类型语言之中,因为弱类型语言会自动推导变量类型。

示例:

http://localhost/sql.php?id=8 AND 1=1

上述的示例多是进行自动化的“盲注”。在后台执行的SQL为:SELECT * FROM test where id  8 AND 1=1。

PS:盲注是在服务器没错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者而言缺少了非常重要的“调式信息”,所以攻击者需要找一个一个方法来验证注入的SQL是否已经执行。盲注是根据多给条件进行判断,如果页面没有出错则表示存在注入点。


2.2 字符型注入

字符型与数字型的最大区别在于,前者一般要是有单引号来闭合,后者不需要。

示例:

http://localhost/sql.php?name=admin and 1=1

这样是无效,因为后端的SQL为:

     

SELECT * FROM test where name = 'admin and 1=1'

如果改为:

http://localhost/sql.php?name='admin' and 1=1

此时后端SQL:SELECT * FROM test where name = 'admin' and 1=1

该类型的注入是公司扫雷重点照顾对象,大家可以看看扫雷的日志多是各种拼接的SQL。


总结一下,字符型注入还可以细分出一下不同的类型,如POST注入、Cookie注入、搜索注入、BASE64注入。。。本质上还是字符型注入,数字型注入也是可以说是特殊的字符型注入。

因为公司已经存在强大的扫雷工具了所以在此就不再介绍别的工具了。


三、防止SQL注入

防SQL注入总结就一句话:有输入的地方就有存在SQL的风险。

3.1 统一输入处理,严格类型判断

金融知心的大多数的项目使用的PHP,弱类型的语言没有强制要求处理数据类型。但防御数字型注入还是很简单的,一个函数 intval ($param);就可以。

$param  = intval ($param);

3.2 特殊字符转义

对于字符类型,他们都是string,所以很难判断输入的是否存在恶意攻击。现在通用的防御方法就是对特殊符号进行转义。

$paramStr = mysql_real_escape_string($paramStr);

最好是框架或者类库在底层提供了而不需要开发人员在业务代码中混合在一起了。

3.3 使用预编译

PHP的PDO也提供了预编译的功能了,只是在我们金融项目中很少使用,因为使用起来不算方便。

3.4 使用框架

在PHP中很多框架都是提供了好用而且安全的数据库操作类库了,知心普遍使用的Topaz使用的mysqli而且SQL语句都是拼接的,所以最近扫雷就会出现类似SQL注入的出现。

3.5 使用存储过程

不建议使用了,虽然使用存储过程可以提高执行效率,因为使用存储过程需要精通SQL,而且不方便经常变更和会出现移植问题。


四、举例说明

最近扫雷经常出现提示有SQL注入的情况,也有的是误报的。也执行到了数据库,仅是此次组装的SQL是错误的(谁知道如果是人工构造时候会不会出现真的注入了呢)。为了安全的考虑,是不允许不符合格式的SQL进入数据库执行的,不论这个语句是否可以真的注入。有可能是SQL漏洞被发觉的是因为我们异常错误的处理不当,不应该再给前端返回后端数据库异常的信息。

出现多次扫雷问题,主要是对参数校验不足和库类使用的方式造成。

如我们看一段代码:

 public static function getLoanArticle ($input, array $arrIds, $bsPriceList, $cate, $nestId = 22){
        $rtn = array();
        
        $db = Env::getDB('fnbiz');
        
        $ids = array();
        foreach ($arrIds as $id)
        {
            $id = $db->real_escape($id);
            if (!empty($id)) {
                $ids[] = $id;
            }
        }

        if (empty($ids))
        {
            Env::getLogger()->warning('DB', 'empty parameter arrIds');
            return $rtn;
        }

        //TODO delete note 
        $tbl = 'knowledge_loan_article';
        $strId = implode(',', $ids);
         //TODO: select count
        $sql = "SELECT articleId, keywords, source, title, brief, lastUpdateTime, articleLink, content, ext FROM $tbl WHERE articleId IN ($strId) AND isDelete = 0 AND nestId = $nestId";
        Env::getLogger()->trace(
                "get articel from db [sql:$sql] [articleId count:" . count($ids) .
                         "]");
        
        //$rs = $db->fetchAll($sql);
        ...  ...


       

    为什么写成这样SQL拼接的方式,是因为Topaz要求这么写的,但在这个SQL语句之前没有对$id进行校验就会有SQL注入。很庆幸,扫雷发现并进行处理了。参数的校验包含了参数类型和参数长度等多种属性的校验。注:安全与成本是成反比的,注意平衡!

    使用UI的一段代码来看看:

    

public static function getByProductIds(array $arrIds) {
		if (empty($arrIds)) {
			Env::getLogger()->warning('DB', 'empty parameter arrIds');
            return array() ;
		}
		$db = Env::getDB('fnbiz');
        $ids = array() ;
        foreach($arrIds as $id ){
            $ids[] = $db->real_escape($id) ;
        }

        $strIds = implode(",",$ids) ;
        $tb = "product";
        $sql = "SELECT * FROM `$tb` WHERE productId IN ( $strIds )";
        $res= $db->fetchAll($sql) ;

        $mapRes = array() ;
        foreach($res as $item){
            $mapRes[$item['productId']] = $item;// json_decode($item['productInfo'], true) ;
        }
        $rtn = array() ;
        foreach($ids as $id){
            if( !isset($mapRes[$id]) ) {
                Env::getLogger()->warning('DB',"product $id not found in db") ;
            }else{
                 $rtn[$id] = $mapRes[$id] ;
            } 
        }
        return $rtn ;
    }

    可以发现参数的转义已经和业务的代码混合在一起了,每一个人的代码风格都不一样这并不可怕,可怕的是每一个人开发人员的安全意识都不一样。有的有参数的转义有的却没有!

    

    建议使用PDO库类替换Topaz并统一语法。