php - 如何防止 PHP 中的 SQL 注入(inject)?

标签 php mysql sql security sql-injection

这个问题的答案是 community effort .编辑现有答案以改进这篇文章。它目前不接受新的答案或交互。








如果用户输入未经修改就插入到 SQL 查询中,那么应用程序将容易受到 SQL injection 的攻击。 ,如以下示例中所示:

$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 注入(inject)攻击的正确方法是 将数据从 SQL 中分离出来, 以便数据保持数据并将 永远不会被解释 作为 SQL 解析器的命令。可以使用格式正确的数据部分创建 SQL 语句,但如果您不完全了解详细信息,则应始终 使用准备好的语句和参数化查询。 这些是与任何参数分开发送到数据库服务器并由其解析的 SQL 语句。这样攻击者就不可能注入(inject)恶意 SQL。
你基本上有两种选择来实现这一点:

  • 使用 PDO (对于任何支持的数据库驱动程序):
     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
  • 使用 MySQLi (对于 MySQL):
     $stmt = $dbConnection->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 以外的数据库,则可以引用驱动程序特定的第二个选项(例如,pg_prepare()pg_execute() 用于 PostgreSQL)。 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 解析(使可能的攻击者没有机会注入(inject)恶意 SQL)。
    虽然您可以设置 charset在构造函数的选项中,重要的是要注意“旧”版本的 PHP(5.3.6 之前)silently ignored the charset parameter在 DSN 中。

    解释
    您传递给 prepare 的 SQL 语句由数据库服务器解析和编译。通过指定参数(可以是 ? 或命名参数,如上例中的 :name),您可以告诉数据库引擎您要过滤的位置。那么当你调用execute ,准备好的语句与您指定的参数值相结合。
    这里重要的是参数值与编译语句组合在一起,而不是 SQL 字符串。 SQL 注入(inject)的工作原理是在创建要发送到数据库的 SQL 时诱使脚本包含恶意字符串。因此,通过将实际 SQL 与参数分开发送,您可以限制以您不想要的东西结束的风险。
    您在使用准备好的语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数也可能最终以数字形式结束)。在上面的例子中,如果 $name变量包含 'Sarah'; DELETE FROM employees结果将只是搜索字符串 "'Sarah'; DELETE FROM employees" , 你不会得到 an empty table .
    使用准备好的语句的另一个好处是,如果您在同一个 session 中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升。
    哦,既然你问过如何插入,这里有一个例子(使用 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';
    }
    

    关于php - 如何防止 PHP 中的 SQL 注入(inject)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23592262/

    相关文章:

    php - 在php中使用post更新记录时,页面会刷新

    S3 上的 Mysql 数据目录

    javascript - 如何将套接字信息自动添加到MySQL

    sql - Oracle 从 1 开始序列

    sql - 如何找到当前的交易水平?

    python - sqlalchemy.exc.MultipleResultsFound : Multiple rows were found when exactly one was required

    PHP::检测 session 是否开始的最佳方法

    php - 为什么我的导航链接仍然带有下划线?即使有 !important 声明

    使用多个查询发送邮件的 PHP (PDO) 文件

    MYSQL - 如果列包含字符 THEN ... ELSE