如何防止SQL注入

所谓SQL注入就是把SQL命令混杂在数据中提交给服务器,最终欺骗服务器达到执行恶意代码得目的。SQL注入的后果严重的,轻则数据泄露,重则重要数据丢失。目前多数互联网应用都或多或少依赖于关系数据库且与用户交互性强。防范SQL注入是所有服务器端程序的必备技能。下面以PHP和MySQL为例解释什么是SQL注入及如何防止。

什么是SQL注入

SQL注入通常是由于对程序在处理动态SQL时没有对用户输入数据进行充分过滤造成的。如下面的例子。

字符串类型注入

    $name = $_POST['user_name'];
    $sql = "select * from users where user_name='$name'";
    mysql_query($sql);

这是一段很常见的读取用户信息的代码。这段代码未对用户输入数据做任何处理,非常容易遭到SQL注入攻击。如果恶意用户输入如下的用户名,用户表将被删除,损失惨重。

    $name = "Steve';drop table users; --";
    $sql = "select * from users where user_name='Steve';drop table users; --'"
    ... ...

数字类型注入

    $id = $_GET['user_id'];
    $sql = "select * from users where user_id=$id";
    mysql_query($sql);

恶意用户可以如下构造GET参数,同样用户表被删除。

    $id = "1; drop table users;";
    $sql = "select * from users where user_id=1; drop table users;";
    ... ...

解决方案

参数化查询

参数化查询指现在SQL语句中使用占位符标记参数,传送SQL语句到数据库进行预编译--词法及语法分析(Prepared Statement),然后再绑定参数执行。使用该方案可以完美的阻击SQL注入。对于PHP来说,无论是使用PDO还是MySQLi都支持参数化查询。

    // PDO
    $st = $pdo->prepare("select * from users where user_name=:name");
    $st->execute(array('name' => $name));
    ... ...
    // MySQLi
    $st = $conn->prepare("select * from users where user_name= ?");
    $st->bind_param('s', $name);
    $st->execute();
    ... ...

对于上文第一例SQL注入,上面两种方案,数据库最终执行的SQL都类似于:

    select * from users where user_name='Steve\';drop table users; --';

这条语句不会返回任何数据,也不会删除任何数据。 这里要注意的是,MySQL的预编译并不是缺省打开的,根据不同的实现,有的需要在建立数据库连接时指定。

规范化参数

通常来说,参数化查询是解决SQL注入的正规军,清晰明了,无懈可击。但是如果SQL语句在一个会话只执行一次,使用参数化方法对性能有一定的影响,特别是对于大容量、高并发的应用来说。下面的方案也可以防止SQL注入,但是需要谨慎使用:()

  • 参数白名单:对于有明确取值范围的参数,进行白名单过滤;
  • 数值类型:检查其合法性,如只能有数字、小数点、正负号等;
  • 字符串型:使用mysql_real_escape_string或类似功能函数对特殊字符做特殊处理;

对于上文第二例SQL注入,只要进行了数值类型检查,就可以避免掉。

后记

防SQL注入也是系统安全的一部分,安全无小事,需要边学习边总结,大家共同进步。