bin
发布于

如何在PHP中防止SQL注入?

如果用户输入直接未经修改地插入到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;--')

如何防止这种情况发生?

浏览 (357)
点赞
收藏
1条评论
Klustron小助手
防止SQL注入攻击的正确方法,无论您使用哪种数据库,都是将数据与SQL分开,这样数据就始终是数据,SQL解析器永远不会将其解释为命令。虽然可以创建带有正确格式化数据部分的SQL语句,但如果您不完全了解细节,应始终使用预处理语句和参数化查询。这些是发送到并由数据库服务器单独解析的SQL语句,与任何参数分开。这样,攻击者就无法注入恶意SQL。 您基本上有两种选择来实现这一点: 1.使用PDO(适用于任何支持的数据库驱动): $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row } 2.使用MySQLi(适用于MySQL): 从PHP 8.2+开始,我们可以使用execute_query(),它将准备、绑定参数和执行SQL语句合为一步: $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]); while ($row = $result->fetch_assoc()) { // Do something with $row } 直到PHP8.1: $stmt = $db->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // Do something with $row } 如果您连接到MySQL以外的数据库,还有特定于驱动程序的第二个选项,例如对PostgreSQL,可以参考pg_prepare()和pg_execute()。PDO是通用选项。 正确设置连接 PDO 注意,使用PDO访问MySQL数据库时,默认不使用真正的预处理语句。要解决这个问题,您必须禁用预处理语句的模拟。使用PDO创建连接的示例是: $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 在上面的示例中,错误模式不是严格必要的,但建议添加它。这样PDO将通过抛出PDOException来通知您所有MySQL错误。 然而,必须设置的是第一行setAttribute(),它告诉PDO禁用模拟的预处理语句并使用真正的预处理语句。这确保了语句和值不会在发送到MySQL服务器之前被PHP解析(给潜在攻击者没有机会注入恶意SQL)。 尽管您可以在构造函数的选项中设置字符集,但重要的是要注意,PHP的“旧”版本(在5.3.6之前)在DSN中静默忽略了字符集参数。 MySQLi 对于MySQLi,我们必须遵循相同的程序: mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test'); $dbConnection->set_charset('utf8mb4'); // charset 解释 您传递给prepare的SQL语句将被数据库服务器解析和编译。通过指定参数(无论是?还是像上面例子中的:name这样的命名参数),您实际上是在告诉数据库引擎您想在哪个部分进行过滤。接下来,当您执行execute时,准备好的语句会与您提供的参数值结合起来。 关键在于,这些参数值会与编译后的语句结合,而不是和SQL字符串结合。SQL注入的原理是诱导脚本在生成发送至数据库的SQL时插入恶意字符串。因此,通过将实际的SQL与参数分离发送,可以大大减少执行非意图操作的风险。 使用预处理语句发送的参数将被当作字符串处理(当然,数据库引擎可能会进行一些优化,因此参数可能最终表示为数字)。如上例所示,如果$name变量包含了'Sarah'; DELETE FROM employees,结果仅会是寻找字符串'Sarah'; DELETE FROM employees,并不会导致表被清空。 使用预处理语句的另一优势在于,如果同一语句在同一会话中多次执行,它只需被解析和编译一次,从而提升执行速度。 此外,关于插入操作的示例,这里有一个使用PDO的示例: $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]); 预处理语句能用于动态查询吗? 尽管您可以对查询参数使用预处理语句,但动态查询本身的结构无法参数化,而且某些查询功能也不能参数化。 在这种情况下,最好的做法是使用一个白名单过滤器来限制可能的值。 // Value whitelist // $dir can only be 'DESC', otherwise it will be 'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }
点赞
评论