3

假设我要执行这样的准备好的语句:

$qry->prepare('UPDATE table_name SET column1 = ? string_column = ? WHERE column3 = ? AND column4 = ?');
$qry->bind_param('sbid', $string, $blob, $int, $double);

$int = 'non int value'; /* gives 0 in the database */
$blob = 'some string';
$string = 'another string';
$double = $double;

$qry->execute();
$qry->close();

假设我只想执行一次查询,我只是以安全的名义使用了准备好的语句。从我一直在阅读的内容来看 - 只使用一次准备好的查询会产生更多开销,这相当于为了安全利益而牺牲性能。话虽如此 - 像这样一次执行相同查询的性能/安全性差异是什么。

$int = (int) $int;
$blob = "'" .mysql_real_escape_string($blob) ."'";
$string = "'" .mysql_real_escape_string($blob) ."'";    
$double = (double) $double;

$db->query("UPDATE SET column1 = $int, column2 = $blob WHERE column3 = $string AND column4 = $double ");

PS。我对 Prepared 语句如何提高性能不感兴趣,但对单个查询的安全性和速度差异感兴趣。

4

3 回答 3

3

谢谢你的好问题。

事实上,您可以同时使用这两种方法。

大多数人确实将准备好的语句的概念与主要 DBMS 提供的 [非常有限的] 实现混淆了。虽然后者可能会受到质疑,但前者确实是唯一的方法。

看看这个例子。让我们使用safeMysql运行您的查询:

$sql = "UPDATE SET column1 = ?i, column2 = ?s WHERE column3 = ?s AND column4 = ?s";
$db->query($sql, $string, $blob, $int, $double);

它像您的代码一样执行字符串格式化,但在内部执行。为什么?因为它是如何在内部实现的(通过本机准备好的语句或手动格式化)并不重要,但是使用准备好的语句来组装您的查询是必不可少的。

大多数人都忽略了关于准备好的陈述的一些要点:

  1. 它使格式化始终完整(尽管在您的示例中您正在做正确的事情并进行了完整的格式化,但仍然容易陷入不完整的格式化,如下所示:

    $colname = "`$colname`";
    
  2. 您的格式始终是正确的。它不会让你做类似的事情

    $colname = "`" .mysql_real_escape_string($colname) ."`";
    

    这将是无用的并导致您注射

  3. 它将强制格式化。通过以您当前的方式组装查询,很容易忽略一两个变量。

  4. 它将尽可能接近查询执行进行正确的格式化。这是非常重要的一点,因为
    • 它不会破坏您的源变量(如果查询失败并且您想回显它怎么办?)
    • 它不会让您将格式化代码从查询中移开,这可能会导致致命的后果。
    • 毕竟,它会让您的代码大大缩短,而无需所有无聊的手动格式化!

这就是准备好的语句的真正好处,它保证了安全性,从而使它们变得如此受欢迎。虽然服务器端准备的东西虽然很聪明,但只是一个特定的实现。

此外,以准备好的语句为指导,可以为可能添加到查询中的所有内容(例如标识符或数组)创建一个占位符,使其真正安全和方便使用。

牢记所有事情,必须在他们的数据库访问库中实现准备好的语句的想法,以使代码安全且简短。

只是来自 safeMysql 的几个例子:

$name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']);
$data = $db->getInd('id','SELECT * FROM ?n WHERE id IN ?a','table', array(1,2));
$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);

$ids  = $db->getCol("SELECT id FROM tags WHERE tagname = ?s",$tag);
$data = $db->getAll("SELECT * FROM table WHERE category IN (?a)",$ids);

$data = array('offers_in' => $in, 'offers_out' => $out);
$sql  = "INSERT INTO stats SET pid=?i,dt=CURDATE(),?u ON DUPLICATE KEY UPDATE ?u";
$db->query($sql,$pid,$data,$data);

只需对传统的 mysql(i) 进行相同的尝试,然后查看它需要的代码量。

您可能会注意到,对于可用的准备好的语句,您必须用类型标记它们,因为类型不仅仅是简单的字符串,而且它是告诉驱动程序如何格式化变量的唯一可靠方法。

于 2013-05-31T05:27:12.007 回答
3

这有很多。一些随机点

  • 单次使用准备好的语句确实会带来(超过理论上的)性能损失,如果与 MySQL 服务器存在大量连接,则性能损失会更高。(思考:上下文切换)
  • 但是您不应该将数据库服务器运行得如此接近其极限,否则会有所不同。
  • 你并不总是有选择(想想:共享主机)

或者:

  • 在某些(甚至很多)情况下,准备好的语句提供安全优势 - 有很多业务逻辑,其中不涉及用户生成的数据(想想:Jointables,仅携带 ID)或用户- 由于其他原因,必须事先验证生成的数据(想想:价格计算、memcached 查找……)
  • 但是为每个查询选择多种样式中的一种会导致代码无法维护。
  • 但这有时是不可避免的(想一想:构造没有准备好的查询支持IN ( )

经常被忽视:

  • 准备好的查询有时会使与 RDBMS 无关
  • 但是准备好的查询提供了针对 SQL 注入的最佳保护。

我最喜欢的:

  • 简单地总是使用准备好的查询是常见的建议
  • 但是这个星球上的大多数生物都会建议你吃粪便或腐烂的有机物质。

因此,风格的选择通常必须根据具体情况进行。我们采用了将所有数据库访问(包括参数管理)封装在标准化库中的方式,即简单的require()ed,因此您可以直接替换为准备好的查询、转义或任何您想要的和您的 RDBMS 支持的内容。

于 2013-05-31T04:05:03.753 回答
1

我相信从安全的角度来看它们同样安全,但是使用prepare不仅使您的 SQL 安全,而且使您感觉安全。您不能始终相信自己手动转义并转换为正确的类型。如果您编写 10,000 个不同的 SQL 查询,您往往会忘记转义一两个。

所以总而言之,prepare对抗 SQL 注入是一个更好的习惯。将PHP变量直接放到SQL查询中让我晚上睡觉时感到不安。

于 2013-05-31T03:55:41.473 回答