5

我有点困惑为什么我们需要指定我们在 PDO 中的 bindParam() 函数中传递的数据类型。例如这个查询:

$calories = 150; 
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->bindParam(1, $calories, PDO::PARAM_INT); 
$sth->bindParam(2, $colour, PDO::PARAM_STR, 12);
$sth->execute();

如果我不指定第三个参数,是否存在安全风险。我的意思是如果我只是在 bindParam() 中做:

$sth->bindParam(1, $calories); 
$sth->bindParam(2, $colour);
4

3 回答 3

3

bindParam()可以认为使用with 类型更安全,因为它允许更严格的验证,进一步防止 SQL 注入。但是,如果您不这样做,我不会说存在真正的安全风险,因为您执行的准备好的语句可以防止 SQL 注入,而不是类型验证。实现这一点的更简单方法是简单地将数组传递给execute()函数而不是使用bindParam(),如下所示:

$calories = 150; 
$colour = 'red';

$sth = $dbh->prepare('SELECT name, colour, calories
                      FROM fruit
                      WHERE calories < :calories AND colour = :colour');

$sth->execute(array(
    'calories' => $calories,
    'colour' => $colour
));

您没有义务使用字典,您也可以像使用问号一样进行操作,然后将其以相同的顺序放入数组中。但是,即使这很有效,我还是建议养成使用第一个的习惯,因为一旦达到一定数量的参数,这种方法就会变得一团糟。为了完整起见,它看起来像这样:

$calories = 150; 
$colour = 'red';

$sth = $dbh->prepare('SELECT name, colour, calories
                      FROM fruit
                      WHERE calories < ? AND colour = ?');

$sth->execute(array($calories, $colour));
于 2013-06-02T08:41:33.627 回答
2

如果不指定类型,则默认将参数绑定为字符串。然后,您可能会得到一个相当于以下内容的查询:

SELECT foo FROM bar WHERE baz = '42'

这可能是也可能不是问题。就像 PHP 和其他编程语言一样,数据库具有类型和类型之间转换的规则。MySQL 通常会根据需要将数字字符串隐式转换为数字。有些数据库比其他数据库更严格,某些操作需要正确的类型。在大多数情况下,它几乎没有什么区别。但是如果你真的需要传递 42 as42而不是'42',你需要将它显式绑定为INT.

于 2013-06-02T09:12:07.437 回答
1

我知道这是一个老问题,但我想深入研究一下,因为在绑定值/参数时我总是忘记数据类型参数的内部工作原理。我也有更多的 MySQL 经验,所以这个答案将更多地与 MySQL 相关。

MySQL 根据需要自动将数字转换为字符串,反之亦然,所以如果你这样做:

SELECT * FROM tablename WHERE columnname = 42

或者

SELECT * FROM tablename WHERE columnname = '42'

MySQL 知道columnname应该是什么列类型,如果提供了错误的类型作为值,它将自动进行类型转换。

现在,查看PDOStatement 的源代码

if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
    if (Z_TYPE_P(parameter) == IS_DOUBLE) {
        char *p;
        int len = zend_spprintf_unchecked(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(parameter));
        ZVAL_STRINGL(parameter, p, len);
        efree(p);
    } else {
        convert_to_string(parameter);
    }
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) {
    convert_to_long(parameter);
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && Z_TYPE_P(parameter) == IS_LONG) {
    convert_to_boolean(parameter);
}

您会从上面的代码中注意到,当绑定语句的第三个参数被省略或PDO_PARAM_STR(默认值)时,PHP 会将值类型转换为字符串。当它提供为PDO_PARAM_INT时,与您实际期望的不同,它不会类型转换为整数,而是 PHP 将其转换为long!所以如果你这样做:

$stmt->bindValue("integer_column", "41.9", PDO_PARAM_INT);

数据库实际上将接收41.9要在查询中使用的 long 值,然后将其四舍五入(至少通过 MySQL)到最接近的整数,因此在上述查询中使用的最终值INTEGER将是42.

mysql> SELECT CAST(41.9 AS UNSIGNED);
+------------------------+
| CAST(41.9 AS UNSIGNED) |
+------------------------+
|                     42 |
+------------------------+

如果您有一个非数字字符串,情况也是如此,例如"41.9asdf"

$sth->bindValue("integer_column_value", "41.9asdf", PDO_PARAM_INT);

它将首先由 PHP ( 41.9) 类型转换为 long,然后该值将由 MySQL ( 42) 舍入为整数。

就像我之前提到的,MySQL 自动将字符串转换为数字,所以如果你给 MySQL 字符串"123",将它转换为整数或任何其他数字类型都没有问题。但是,请记住,当 MySQL 从字符串转换为整数时,它会截断而不是舍入,因此这可能是绑定时提供类型之间的实际差异。

mysql> SELECT CAST('123.6' AS UNSIGNED);
+---------------------------+
| CAST('123.6' AS UNSIGNED) |
+---------------------------+
|                       123 |
+---------------------------+

mysql> SELECT CAST(123.6 AS UNSIGNED);
+-------------------------+
| CAST(123.6 AS UNSIGNED) |
+-------------------------+
|                     124 |
+-------------------------+

因此,以下是 PHP 中整数列将包含的值:

$stmt->bindValue("integer_column", "123.6", PDO_PARAM_INT); // 124
$stmt->bindValue("integer_column", "123.6"); // 123
$stmt->bindValue("integer_column", (int) "123.6"); // 123
$stmt->bindValue("integer_column", round("123.6")); // 124

请注意,您可能会从上面注意到,PHP 将字符串转换为整数的方式与 MySQL不同。MySQL 舍入浮点数,但 PHP 将使用该floor值:

var_dump((int) "123.6"); // int(123)
var_dump((int) 123.6); // int(123)

现在,要实际回答您问题的安全方面,将第三个参数提供给准备好的语句没有任何安全优势。准备好的语句本身就足以在正确完成时减轻 SQL 注入(有关一些晦涩的边缘情况,请参阅此链接)。

于 2018-06-02T23:34:41.160 回答