如果用户输入未经修改就插入到 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;--')
可以做些什么来防止这种情况发生?
原文由 Andrew G. Johnson 发布,翻译遵循 CC BY-SA 4.0 许可协议
无论您使用哪个数据库,避免 SQL 注入攻击的 正确 方法是 将数据与 SQL 分离,以便数据保持数据, 永远不会被 SQL 解析器解释 为命令。可以使用格式正确的数据部分创建 SQL 语句,但如果您不 完全 了解详细信息,则应始终 使用准备好的语句和参数化查询。 这些是与任何参数分开发送到数据库服务器并由其解析的 SQL 语句。这样攻击者就不可能注入恶意 SQL。
你基本上有两种选择来实现这一点:
如果您要连接到 MySQL 以外的数据库,则可以参考特定于驱动程序的第二个选项(例如,
pg_prepare()
和pg_execute()
对于 PostgreSQL)。 PDO 是通用选项。正确设置连接
PDO
请注意,当使用 PDO 访问 MySQL 数据库时 ,默认情况下不使用 实际 准备好的语句。要解决此问题,您必须禁用准备好的语句的模拟。使用 PDO 创建连接的示例是:
在上面的例子中,错误模式不是绝对必要的, 但建议添加它。这样,PDO 将通过抛出
PDOException
来通知您所有 MySQL 错误。然而, 强制性 的是第一个
setAttribute()
行,它告诉 PDO 禁用模拟的准备好的语句并使用 真正的 准备好的语句。这确保了语句和值在将其发送到 MySQL 服务器之前不会被 PHP 解析(使可能的攻击者没有机会注入恶意 SQL)。尽管您可以在构造函数的选项中设置
charset
,但重要的是要注意 PHP 的“旧”版本(5.3.6 之前) 会默默地忽略 DSN 中的 charset 参数。mysqli
对于 mysqli,我们必须遵循相同的程序:
解释
您传递给
prepare
的 SQL 语句由数据库服务器解析和编译。通过指定参数(在上面的示例中,可以是?
或命名参数,如:name
),您可以告诉数据库引擎您要过滤的位置。然后,当您调用execute
时,准备好的语句将与您指定的参数值相结合。这里重要的是参数值与编译语句组合在一起,而不是 SQL 字符串。 SQL 注入的工作原理是在创建要发送到数据库的 SQL 时诱使脚本包含恶意字符串。因此,通过将实际 SQL 与参数分开发送,您可以限制以您不想要的东西结束的风险。
您在使用准备好的语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数也可能最终以数字形式结束)。在上面的示例中,如果
$name
变量包含'Sarah'; DELETE FROM employees
结果将只是搜索字符串"'Sarah'; DELETE FROM employees"
,并且您不会 以空 结尾 表。使用准备好的语句的另一个好处是,如果您在同一个会话中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升。
哦,既然你问过如何插入,这里有一个例子(使用 PDO):
准备好的语句可以用于动态查询吗?
虽然您仍然可以为查询参数使用准备好的语句,但动态查询本身的结构无法参数化,某些查询功能也无法参数化。
对于这些特定场景,最好的办法是使用限制可能值的白名单过滤器。