如果用户输入未经修改就插入到SQL查询中,则应用程序很容易受到SQL注入的攻击,如下例所示:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

这是因为用户可以输入类似值的内容);DROP TABLE表;--,并且查询变为:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

可以采取什么措施防止这种情况发生?


当前回答

无论你最终使用的是什么,确保你的输入没有被magic_quotes或其他善意的垃圾破坏,如果有必要的话,通过条带斜杠或其他方式来清理它。

其他回答

这个问题的简单替代方案可以通过在数据库本身中授予适当的权限来解决。例如:如果您使用的是MySQL数据库,则通过终端或提供的UI输入数据库,然后执行以下命令:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

这将限制用户只能受限于指定的查询。删除删除权限,这样数据就永远不会从PHP页面发出的查询中删除。第二件事是刷新特权,以便MySQL刷新权限和更新。

FLUSH PRIVILEGES; 

有关刷新的详细信息。

要查看用户的当前权限,请启动以下查询。

select * from mysql.user where User='username';

了解更多有关GRANT的信息。

要使用参数化查询,需要使用Mysqli或PDO。要用mysqli重写示例,我们需要以下内容。

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("server", "username", "password", "database_name");

$variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();

你想在那里读到的关键函数是mysqli::prepare。

此外,正如其他人所建议的,您可能会发现使用PDO之类的东西来提升抽象层是有用的/更容易的。

请注意,您询问的案例相当简单,更复杂的案例可能需要更复杂的方法。特别地:

如果您希望根据用户输入更改SQL的结构,参数化查询将不会有帮助,并且mysql_real_ascape_string不包含所需的转义。在这种情况下,最好通过白名单传递用户的输入,以确保只允许通过“安全”值。

安全警告:此答案不符合安全最佳实践。转义不足以防止SQL注入,请改用准备好的语句。

SQL语句中转义特殊字符的一些准则。

不要使用MySQL。此扩展已弃用。请改用MySQLi或PDO。

MySQLi

对于手动转义字符串中的特殊字符,可以使用mysqli_real_escape_string函数。除非使用mysqli_set_charset设置了正确的字符集,否则该函数将无法正常工作。

例子:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

要使用准备好的语句自动转义值,请使用mysqli_prepare和mysqli_stmt_bind_param,其中必须提供相应绑定变量的类型以进行适当的转换:

例子:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

无论您使用prepared语句还是mysqli_real_escape_string,您都必须知道正在使用的输入数据的类型。

因此,如果使用准备好的语句,则必须指定mysqli_stmt_bind_param函数的变量类型。

正如名字所说,mysqli_real_escape_string的使用是为了转义字符串中的特殊字符,因此它不会使整数安全。此函数的目的是防止破坏SQL语句中的字符串,以及它可能对数据库造成的损坏。如果使用得当,mysqli_realescape_string是一个有用的函数,尤其是与sprintf结合使用时。

例子:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

无论你最终使用的是什么,确保你的输入没有被magic_quotes或其他善意的垃圾破坏,如果有必要的话,通过条带斜杠或其他方式来清理它。

一个好主意是使用像Idiorm这样的对象关系映射器:

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

它不仅可以避免SQL注入,还可以避免语法错误!它还支持具有方法链接的模型集合,以一次过滤或将操作应用于多个结果和多个连接。