如何在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代码。

有两种方式去完成这个:

  1. 使用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'; 
阅读更多

没有更多推荐了,返回首页