如果用户的输入未经修改就插入到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;--')
怎样才能防止这种情况发生?
弃用警告: 这个答案的示例代码(和问题的示例代码一样)使用了PHP的
MySQL
扩展,该扩展在PHP 5.5.0中被废弃,在PHP 7.0.0中被完全删除。
安全警告。此答案不符合安全最佳实践。Escaping不足以防止SQL注入,请使用prepared statements代替。使用下面的策略,风险自负。(另外,
mysql_real_escape_string()
在PHP7中被删除。)
如果你使用的是最新版本的PHP,下面概述的mysql_real_escape_string
选项将不再可用(尽管mysqli::escape_string
是一个现代的等价物)。现在,mysql_real_escape_string
选项只对旧版本的PHP上的传统代码有意义。
你有两个选择--转义unsafe_variable
中的特殊字符,或者使用参数化查询。这两种方法都可以保护你免受SQL注入。参数化查询被认为是更好的做法,但在使用它之前,需要在PHP中改变成一个新的MySQL扩展。
我们将首先介绍影响较小的字符串转义。
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
另见,mysql_real_escape_string
函数的细节。
要使用参数化查询,你需要使用MySQLi而不是MySQL函数。要重写你的例子,我们需要类似以下的东西。
<?php
$mysqli = new mysqli("server", "username", "password", "database_name");
// TODO - Check that connection was successful.
$unsafe_variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// TODO check that $stmt creation succeeded
// "s" means the database expects a string
$stmt->bind_param("s", $unsafe_variable);
$stmt->execute();
$stmt->close();
$mysqli->close();
?>
你要阅读的关键函数是mysqli::prepare
。
另外,正如其他人所建议的,你可能会发现用PDO这样的东西来增加一层抽象是有用的/更简单的。
请注意,你问的是一个相当简单的案例,更复杂的案例可能需要更复杂的方法。特别是。
mysql_real_escape_string
所能涵盖的。在这种情况下,你最好通过白名单来传递用户的输入,以确保只有'安全'值被允许通过。mysql_real_escape_string
的方法,你将遭受Polynomial在下面评论中描述的问题。这种情况比较棘手,因为整数不会被引号所包围,所以你可以通过验证用户输入只包含数字来处理。我建议使用PDO(PHP数据对象)来运行参数化的SQL查询。
这不仅可以防止SQL注入,而且还可以加快查询速度。
通过使用PDO而不是mysql_
, mysqli_
, 和pgsql_
函数,你可以使你的应用程序从数据库中抽象出来,在你不得不切换数据库供应商的罕见情况下。
你可以做一些像这样的基本工作。
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
这并不能解决所有问题,但它是一个很好的垫脚石。我漏掉了一些明显的项目,如检查变量的存在、格式(数字、字母等)。