目标:编写动态的SQL查询
动态查询即是指将变量放到语句中,将固定的字符串和变量拼接在一起,组成一个完整的SQL查询语句,由于变量的值是动态变化的,因此查询也是动态的。编写这样的能够执行的动态的查询语句是十分自然的,也非常方便。但是,不经思考的加入也会带来很大的安全隐患。例如这样的一个查询:
SELECT × FROM Bugs WHERE bug_id = $bug_id";
如果这个时候$bug_id = “1234; DELETE FROM Bugs”,Bugs表就跪了。因此SQL注入的危害相当大,不能将未经验证的输入作为代码执行。
做法
没有一种技术能够完全的抵抗SQL注入,因此,需要将多种做法结合起来
转义
转移能防止一些意外的情况,例如这个查询:
SELECT * FROM Projects WHERE project_name = '$project_name'
如果$project_name = O’Hare(这是很正常的一种情况),由于名字里面有个引号,查询就变成:
SELECT * FROM Projects WHERE project_name = 'O'Hare'
这个时候数据库就会因为多了个引号而出错,因此需要在名字里面的’前加反斜杠。
参数化查询
参数化查询是指在编写查询语句的时候,在需要参数的位置上使用参数占位符,然后查询的时候提供这一参数。以php为例:
<?php
$sql = "SELECT * FROM Projects WHERE project_name = ?";
$stmt = mysqli_prepare($con, $sql);
mysqli_stmt_bind_param($stmt, 's', $_POST['name']);
mysqli_stmt_execute($stmt);
简单来说,数据库接到一个指令,大致是这么做的:
编译SQL生成执行计划
选择执行计划
执行
SQL注入很多情况是增加查询语句,增加查询语句就会造成查询的语义发生变化(就是说编译会生成不同的东西)。如果使用参数化查询,在准备查询语句的时候数据库就去编译,然后提供参数就会重用这个编译结果,即使使用了注入变量,语义也不会发生变化(不会重新编译),同时,传参的时候,数据库也会将变量进行过滤,以达到防止注入的效果。
参数化查询也有做不到的东西:
1.多个值的列表不能作为单一参数
如果编写这么一个查询:
SELECT * FROM Bugs WHERE bug_id IN (?)
那么你传递参数的时候不能是“1234,3456,5678”这样的,这样会被认为是一个字符串
2.表名,列名,SQL关键字不能作为参数
过滤输入内容
应该将所有不合法的字符去掉,而不是找是否有些输入包含了危险的内容。
比如说如果是需要一个整数,就应该只是用变量里面的整数部分,(在Php里面有filter扩展),可以直接用类型转换函数,也可以用正则表达式匹配
给动态输入的值加引号
一般都用参数化查询,但是有时候,参数化查询会导致数据库采用错误的优化方案(可能是采用错误的索引)。比如说表里面有一列99%的都是TRUE,那么用所以很快就能查询到那1%的FALSE。这个时候将变量直接插进去是好的做法。但这个时候就要格外小心的引用字符串。在php里面可以使用PDO::quote()
用户与代码的隔离
比如说,你想要让用户选择到底是按什么关键字排序,是升序排序,还是按降序排序,这个时候你从客户端接受两个参数
<?php
$sort_standard = $_POST['order'];
$direction = $_POST['direction'];
$sql = "SELECT * FROM Bugs ORDER BY $sort_standard $direction";
mysqli_query($con, $sql);
但是这个并不是一个安全的做法,因为用户可以传递任意值。但是参数化查询又不能是关键字,采用这样的做法:
先声明$sort_standard和$direction数组,比如这样
$sort_standard = array(
"status" => "status",
"date" => "date_report"
);
$direction = array(
"up" => "ASC",
"down" => "DESC"
);
当判断到用户的选择不在数组中的时候就使用默认值,如果在数组中就用对应值,这样做:
从不使用用户的输入进行查询,减少了注入风险
让语句动态化,而没有语法上的限制
将数据库查询和用户界面解藕