SQL注入原理及防范

    前段时间部门遇到SQL注入攻击,在此,我也分享一下自己的经验和理解。

    首先一个很重要的论点:SQL注入是可以完全杜绝的

SQL注入原因

    通俗点讲,SQL注入的根本原因是: "用户输入数据"意外变成了代码被执行。

    "用户输入数据"我这里可以指Web前端$_POST,$_GET获取的数据,也可以指从数据库获取的数据,当然也不排除程序猿无意中使用的特殊字符串。

    在SQL语句的拼接中,一些含特殊字符的变量在拼接时破坏了SQL语句的结构,导致"用户输入数据"意外变成了代码被执行。

SQL语句拼接

    以PHP语言为例,PHP中SQL拼接方式主要有以下几种

1.  数值类型 直接拼接

例如
$sql = 'SELECT * from table where id = '. $id
                 $sql = "SELECT * from table where id = $id ";

这里开发者默认将$id当做数值类型,进行直接拼接。调试时,当$id为字符串时会报错,所以可以提前发现拼接错误。通常会用intval($id),floatval($id)强制转换成数值型,并不会造成SQL注入。

2.  字符串型拼接

例如
$sql = 'SELECT * from table where name = '."$name"
        $sql = "SELECT * from table where name = '$name' ";

这里开发者默认将$name当做字符串处理。当$name中含有单引号(')或 双引号("),会抵消掉$name变量上的单引号或双引号,从而破坏了$sql的结构使得$name有可能变成SQL命令被执行,这也是为什么一些字符转义函数如addslashes()可以防止简单的SQL注入。

除了$name直接含有单/双引号('/")外,由于一些特殊的编码(如特定的汉子字符)也可以抵消$name变量上的单引号或双引号,所以addslashes()、mysql_escape_string()、mysql_real_escape_string() 这种转义特殊字符的方法是有风险的。

但无论编码情况多么复杂,SQL注入有一点是不变的:  抵消 单/双引号,从而破坏SQL语句结构。理解这一点,可以指导开发者编码,从而加强开发者对SQL注入风险的控制能力。

防止SQL注入方法

      在此我把方法归纳为3类。

    1.字符(串)过滤法  (个人认为是网上的一种伪方法,注入后减小损失,以自我安慰,其实已经注入了)

 通常过滤字符串变量中一些敏感词和字符,如" '|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|, "等,或者用正则表达式过滤传入的参数,类似的方法网上有较多参考。

       但是个人觉得字符(串)过滤法并不适合防止SQL注入,因为字符(串)过滤对防止破坏SQL语句结构毫无用处(过滤单/双引号除外),字符(串)过滤法更侧重SQL语句结构破坏后,阻止侵入者执行他的代码意图。(而实际上自己的SQL语句结构已经被破坏了)。

   个人认为这是一种伪方法,是把字符过滤和防止SQL注入两个概念混淆。字符串过滤更是业务层面上的要求,过滤用户输入的'insert ,and,delete'等关键词是否符合业务需是需要考虑的。 

    2.字符转义法  (不推荐使用,无法避免编码注入风险)

       addslashes()    (不推荐使用,只能防止 单/双 引号注入)

         addslashes()是在 单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符)前加上了反斜线,但是遇到一些特殊编码也无能为力。如:含0xbf27 的字就不会,所以 select * from table where name='缞' OR 1 就会产生SQL注入。在上面的SQL拼接中 $sql = " SELECT * from table where name = '$name' "中,如果$name = "'缞' or 1", 用$name = addslashes($name) 还是会产生SQL注入.

    mysql_escape_string()、mysql_real_escape_string()   (不推荐使用,无法避免编码注入风险,且只适用于MySQL)

        mysql_escape_string 和 mysql_real_escape_string功能一样,都是在以下字符前添加反斜杠: \x00, \n, \r, \, ', " 和 \x1a。mysql_real_escape_string使用之前要先连接上数据库,会考虑当前链接connection字符集。

      字符转义法并不能完全避免SQL注入风险,应谨慎使用。

      由于一些复杂的业务逻辑,无法完全避免SQL语句拼接,在变量可控的情况下,使用这种字符转义法也未尝不可,比不使用直接拼接要强得多。

    3.预处理语句法  (解析协议层面上完全规避SQL注入)

         PDO

$pdo = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');  
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');  
$stmt->execute(array('name' => $name));  
foreach ($stmt as $row) {
}

         MYSQLi

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
}

      预处理语句方法会先解析SQL语句,然后通过传入不同的参数值来执行SQL,不需要每次执行都解析SQL语句,查询执行路径比常规查询短,所以预处理语句方法效率更高;预处理语句在SQL语句解析协议上避免将参数当做SQL命令执行,仅仅当做值传递,所以可以完全避免SQL注入。

    总之,预处理语句方法可以完全避免SQL注入,并且效率更高。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章