【问题标题】:Can someone clearly explain why mysqli_prepare()/bind_param() is better than real_escape_string()? [duplicate]有人能清楚地解释为什么 mysqli_prepare()/bind_param() 比 real_escape_string() 更好吗? [复制]
【发布时间】:2013-09-09 22:40:44
【问题描述】:

好吧,我还是不太明白。我一直在阅读,为了正确地转义您的 MySQL 查询,您需要使用 mysqli_prepare()mysqli_bind_param()

我尝试使用此设置,坦率地说,它有点笨拙。当我不需要再次引用它们时,我被困在通过引用传递变量,而完成相同任务只需更多的代码行。

我想我只是不明白两者之间的区别:

<?php
$sql = new \MySQLi(...);
$result = $sql->query('
   UPDATE `table`
   SET
      `field` = "'.$sql->real_escape_string($_REQUEST[$field]).'";
');
?>

<?php
$sql = new \MySQLi(...);
$stmt = $sql->prepare('
   UPDATE `table`
   SET
      `field` = ?;
');
$value = $_REQUEST[$field];
$stmt->bind_param('s', $value);
$stmt->execute();
$result = $stmt->get_result();
unset($value);
?>

除了更多的代码。

我的意思是,他们是否实现了这一点,以便人们在将值发送到查询之前不会忘记转义值?还是它更快?

或者当我打算重复使用同一个查询(因为mysqli_stmt可以重复使用)并在其他情况下使用传统方法时,我应该使用这种方法吗?

【问题讨论】:

  • 据我所知,准备好的语句并不是为了防止 sql 注入而设计的,它们只是因为它们的工作方式才这样做。据我了解,它们旨在让您循环查询时听到较少的声音(只有变量随执行一起发送,而不是查询本身)。不过我可能是错的,如果我错了,我很乐意被告知。
  • 在第二个示例中更让我“跳出来”的是 SQL 文本中没有 WHERE 子句。这在第二个陈述中更容易发现,在第一个陈述中更加伪装。但是对于第一个示例中的那个变量,包装在 mysqli_real_escape_string 函数中,至少我知道 WHERE 子句不在变量中。如果它没有被包装(因为我们看到了太多的例子),我不会知道,看看那个语句,那个变量是否包含 WHERE 子句。

标签: php mysql mysqli prepared-statement mysql-real-escape-string


【解决方案1】:

我认为与其鼓励逻辑分离,不如说后一种方法更安全本身。使用准备好的语句,SQL 查询独立于我们使用的值。这意味着,例如,当我们返回并更改查询时,我们不必将一堆不同的值连接到一个字符串,并且可能会冒忘记转义输入的风险。使代码更易于维护!

【讨论】:

    【解决方案2】:

    我发现有几个主要好处写得很好:

    1. 产生编译和优化语句的开销 只有一次,尽管该语句被执行了多次。不是 所有优化都可以在准备好的语句时执行 编译,有两个原因:最好的计划可能取决于 参数的具体值,最佳方案可能会随着 表和索引会随着时间而变化。
    2. 准备好的语句可以抵御 SQL 注入,因为 参数值,稍后使用不同的方式传输 协议,不需要正确转义。如果原来的说法 模板不是从外部输入派生的,SQL注入不能 发生。

    另一方面,如果一个查询只执行一次,服务器端准备好的语句可能会因为到服务器的额外往返而变​​慢。实施限制也可能导致性能损失:某些版本的 MySQL 没有缓存准备好的查询的结果,而某些 DBMS(例如 PostgreSQL)在执行期间没有执行额外的查询优化。

    Source

    【讨论】:

      【解决方案3】:

      我想从 PHP 5.4.0 开始添加 mysqli_bind_param() has been removed 。你应该使用mysqli_stmt_bind_param()

      【讨论】:

      • 将其作为评论可能比答案更好。我知道,我只是输入了错误的函数名,无论如何我总是采用 OOP 方法。
      【解决方案4】:

      您正在阅读的内容,您需要使用 mysqli_prepare()mysqli_bind_param() 函数来“正确转义您的 MySQL 查询”是错误的。

      确实,如果您使用 mysqli_prepare()mysqli_bind_param(),您不需要(也不应该)“转义”作为绑定提供的值参数。所以,从这个意义上说,你所阅读的内容是有道理的。

      只有当 SQL 文本(查询的实际文本)中包含不安全变量时,您才需要“正确转义”变量,通常通过将变量包装在 mysqli_real_escape_string() 函数中来电。

      (我们注意到可以使用准备好的语句,并且仍然在 SQL 文本中包含未转义的变量,而不是将变量值作为 bind_parameters 传递。这确实有点挫败使用预准备语句的目的,但关键是,无论哪种方式,您都可以编写易受攻击的代码。

      MySQL 现在支持“服务器端”预处理语句(如果在连接中启用了该选项),这是重复执行相同 SQL 文本的性能优化(在某些情况下)。 (这在 Oracle 等其他数据库中得到了长期支持,在这些数据库中,使用准备好的语句一直是一种熟悉的模式。)

      问:他们是否实施了 [prepared statements],以便人们不会忘记在查询中发送值之前对其进行转义?

      答:尽管有关于mysql_real_escape_string() 函数的文档,但根据不使用预准备语句时易受SQL 注入攻击的代码示例的数量,您认为这肯定是充分的理由。

      我认为一个很大的好处是,当我们阅读代码时,我们可以将 SQL 语句视为单个字符串文字,而不是一堆变量的串联,带有引号和点以及对 mysql_real_escape_string 的调用,这是一个简单的查询还不错,但是对于一个更复杂的查询,它只是过于繁琐。 ? 占位符的使用使 SQL 语句更易于理解,... 确实,我需要查看其他代码行以找出其中填充了什么值。 (我认为 Oracle 风格的命名参数 :fee, :fi, :fo, :fum 比位置 ?, ?, ?, ? 表示法更可取。)但是拥有 STATIC SQL 文本才是真正的好处。

      问:或者说它更快?

      正如我之前提到的,使用服务器端准备好的语句可以在性能方面具有优势。它并不总是更快,但对于重复执行相同的语句,唯一的区别是文字值(如重复插入),它可以提供性能提升。

      问:还是应该在我打算重复使用同一个查询时使用这种方法(因为一个mysqli_stmt可以重复使用)而在其他情况下使用传统方法?

      这取决于你。我的偏好是使用 STATIC SQL 文本。但这确实来自使用 Oracle 的悠久历史,并且自然而然地使用与 MySQL 相同的模式。 (尽管来自使用 DBI 接口的 Perl,以及使用 JDBC 和 MyBATIS 或其他 ORM(Hibernate、Glassfish JPA 等)的 Java。

      在 PHP 中遵循相同的模式感觉很自然; mysqli_ 和 PDO 的引入是对神秘(和被滥用)的 mysql_ 接口的一种可喜解脱。

      可以按照任何一种模式编写好的代码。但我挑战你提前思考,关于更复杂的 SQL 语句,以及是否选择使用 mysqli_real_escape_string() 并将要执行的动态字符串连接在一起,而不是使用静态 SQL 文本和绑定参数,对于那些发现自己在维护自己未编写的代码的灵魂来说,阅读和破译正在执行的实际 SQL 可能会变得更加复杂。

      我认为研究表明,阅读代码的次数是编写代码的十倍,这就是我们努力编写可读、可理解的代码的原因,即使这意味着更多的代码行。 (当每个语句都在做一件可识别的事情时,这通常比在一个复杂的语句中阅读一堆串联的函数调用更容易理解。

      【讨论】:

      • 在了解mysqli_stmt_bind_param() 之前,我编写了另一个函数来转义我的SQL 变量,我发现它支持的变量类型比bind_param 支持的多(例如,如果您编写自己的函数,您可以将DateTime 对象自动转换为DATETIME 格式的字符串)。我认为,如果我提前遍历属性(例如foreach ($properties as &amp;$property) { escape_fn($property); } $query = "INSERT INTO table ($properties[0], $properties[1], ...);";,我可以实现同样的清晰度。确定答案thx
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-28
      • 1970-01-01
      • 2012-02-14
      • 2022-01-22
      • 2014-08-29
      • 1970-01-01
      相关资源
      最近更新 更多