我有点混淆为什么我们需要指定我们在Php中的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();
如果我没有指定第3个参数,是否存在安全风险。我的意思是,如果我只是在bindParam():
$sth->bindParam(1, $calories);
$sth->bindParam(2, $colour);
答案 0 :(得分:3)
使用bindParam()
类型可以被认为更安全,因为它允许更严格的验证,进一步防止SQL注入。但是,如果您不这样做,我不会说存在真正的安全风险,因为事实上您执行了prepared statement以防止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));
答案 1 :(得分:2)
如果未指定类型,则默认情况下该参数将绑定为字符串。然后,您可能会得到一个等同于:
的查询SELECT foo FROM bar WHERE baz = '42'
这可能是也可能不是问题。就像PHP和其他编程语言一样,数据库在类型之间具有类型和转换的规则。 MySQL通常会根据需要隐式地将数字字符串转换为数字。有些数据库比其他数据库更严格,要求某些操作使用正确的类型。在大多数情况下,它没什么区别。但是,如果您确实需要将{42}作为42
传递而不是'42'
,则需要将其显式绑定为INT
。
答案 2 :(得分:1)
我知道这是一个老问题,但我想进行一些挖掘,因为我总是忘记绑定值/参数时数据类型参数的内部工作原理。此外,我对MySQL有更多的经验,因此这个答案将与MySQL更相关。
MySQL会根据需要自动将数字转换为字符串,反之亦然,所以如果你这样做:
SELECT * FROM tablename WHERE columnname = 42
或
SELECT * FROM tablename WHERE columnname = '42'
MySQL知道列类型columnname
应该是什么,并且如果提供了错误的类型作为值,它将自动键入cast。
现在,看the source code for 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);
}
从上面的代码中你会注意到,当bind语句的第三个参数被省略或PDO_PARAM_STR
(默认值)时,PHP会将值转换为字符串。当它以PDO_PARAM_INT
提供时,与您实际期望的不同,它不将类型强制转换为整数,而是PHP将其强制转换为 long !所以如果你这样做:
$stmt->bindValue("integer_column", "41.9", PDO_PARAM_INT);
数据库实际上会收到要在查询中使用的长值41.9
,然后舍入(至少由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注入(参见this link for some obscure edge-cases)。