如何在PHP中防止SQL注入?
如何在PHP中防止SQL注入?
stackoverflow上php中得票最高的一个问题,原文链接
http://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php
如需转载请注明原文和译文的链接谢谢
高翔翻译
Q:如果把用户输入的没有任何改动的放到SQL的查询语句中,很有可能会导致SQL注入,比如说下面的例子:
$unsafe_variable = $_POST["user_input"];
mysql_query("INSERT INTO `table` (`column`) VALUES ("$unsafe_variable")");
为什么会有注入漏洞呢?因为用户可以输入value"); DROP TABLE table;--
然后查询语句就变成了这样
INSERT INTO `table` (`column`) VALUES("value"); DROP TABLE table;--")
到底我们能做什么去防止sql注入呢?
A:通过使用预编译语句(prepared statements)和参数化查询(parameterized queries)。这些sql语句从参数,分开的发送到数据库服务端,进行解析。这样黑客不可能插入恶意sql代码。
有两种方式去完成这个:
- 使用PDO对象(对于任何数据库驱动都好用)
$stmt = $pdo->prepare("SELECT * FROM employees WHERE name = :name");
$stmt->execute(array("name" => $name));
foreach ($stmt as $row) {
// do something with $row
}
2. 使用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()) {
// do something with $row
}
如果你链接的数据库不是mysql,你可以参考具体数据库所提供的其他选项,例如(pg_prepare() and pg_execute() for PostgreSQL)
Pdo是一个通用的选项。
正确的建立起连接
注意:当使用PDO去连接Mysql数据库时,真正的预处理默认并没有开启。为了开去他,你应该关闭模拟的预处理语句,以下是一个例子:
$dbConnection = new PDO("mysql:dbname=dbtest;host=127.0.0.1;charset=utf8", "user", "pass");
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
在上面的例子里,错误模式严格意义上来说没有必要,但推荐你加上去。这样,脚本在遇到致命错误(Fatal Error)的时候并不会停止运行。并且给开发者去捕获(catch )那些PDOException异常。
第一个setAttribute()是必须的。这告诉PDO去关闭模拟预处理,然后使用真正的预处理语句。这将保证语句和值在被交到Mysql服务器上没有被解析(让攻击者没有机会去进行sql注入。)
尽管你可以在构造函数里设置字符集(charset ),但你也要注意旧版本的PHP(<5.3.6)会忽略在DSN中设置的字符集参数。
解释
到底发生了什么呢?你的SQL语句交给prepare 之后被数据库服务器解析和编译了。通过制定参数(不管是?还是命名占位符:name),你都可以告诉数据库引擎哪里你想过滤掉。然后当你执行execute方法时,预处理语句会把你所指定的参数值结合袭来。
这里很重要的就是参数值和编译过的语句绑定在了一起,而不是简简单单的SQL字符串、SQL注入通过骗起脚本加入一些恶意的字符串,在建立sql发送到数据库的时候产生后果。所以,通过分离的从参数中发送真正的sql语句,你控制了风险:在结尾的时候你不打算干的一些事。(译者注:请看开篇的例子)。当你使用预编译的时候,任何参数都会被当作字符串。在这个例子里,如果$name变量包含了’Sarah’; DELETE FROM employees 这个结果只会简单的搜索字符串“‘Sarah’; DELETE FROM employees”,所以你不会得到一张空表。
另外一个使用预编译的好处就是,如果你在同一个会话中执行一个statement多次,只会被解析和编译一次,对速度更友好。
哦,既然你问了增加语句的时候怎么使用,下面给你个例子:
$preparedStatement = $db->prepare("INSERT INTO table (column) VALUES (:column)");
$preparedStatement->execute(array("column" => $unsafeValue));
预处理可以被用作动态查询吗
当你仍然使用预处理作为查询的参数是,动态查询的结构不能被参数化,自然查询特征不能被参数化,所以最好的就是设置一个白名单过滤器去限制可能的值。
// Value whitelist
// $dir can only be "DESC" or "ASC"
$dir = !empty($direction) ? "DESC" : "ASC";