我最近询问了一些有关针对SQL注入漏洞的安全性的问题。我决定创建一个使用预准备语句进行sql查询的函数,这样我就不必为每个查询写出如此多的代码行:
function secure_sql($query, $values, $datatypes){
global $link;
$stmt = mysqli_prepare($link, $query);
foreach($values as &$value){
$value = mysqli_real_escape_string($link, $value);
mysqli_stmt_bind_param($stmt, substr($datatypes, 0), $value);
$datatypes = substr($datatypes, -(strlen($datatypes)-1));
}
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, implode(", ", $values));
$results = mysqli_stmt_get_result($stmt);
return $results;
}
其中query是准备好的查询(带有?s),$ values是替换占位符的所有变量的数组(按顺序),$ datatypes是包含变量的所有数据类型的字符串。 $ link是一个数据库连接。
所以我有两个主要问题。
1)它不起作用。我认为这必须是因为在这种情况下可能没有正确使用内爆。我会用什么呢?我不能使用call_user_func_array,因为我还需要将$ stmt作为参数。我已经尝试使用array_unshift将$ stmt添加到参数的开头,但它不起作用。
2)如果我确实让它发挥作用,可以采取哪些措施来改善它?我仍然是PHP和SQL的初学者。
编辑:我现在已经解决了这个问题。谢谢大家的有益评论和答案!答案 0 :(得分:1)
不是直接给你我提出的代码,我觉得解释它是必要的,看看你的一些思维方式是不正确的。
首先,mysqli_stmt_bind_param()
的使用让我困惑 - 你的第二个参数表达式(substr($datatypes, 0)
)返回所有数据类型的值,但你只绑定一个。我认为你打算提出的是:
mysqli_stmt_bind_param($stmt, $datatypes[0] /* <-- retrieves the first character */, $value);
但更重要的是,你应该只调用mysqli_stmt_bind_param()
一次,这会给你带来更大的困难......要省略foreach
- 循环,{{1 }}? (实际上,将call_user_func_array()
作为参数很有可能):
$stmt
如果您对上述内容感到困惑,请以这种方式查看:
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $values));
//calls mysqli_stmt_bind_param($stmt, $datatypes, $values[0], $values[1]... values[n]);
但是,这需要您的call_user_func_array //a call to mysqli_stmt_bind_param, with all the appropriate parameters
(
"mysqli_stmt_bind_param", //the function to call
array_merge //an array consisting of the statement, the datatype and all the values
(
array($stmt, $datatypes), //statement and datatype-parameters
$values //all the values
)
);
- 数组由引用组成,因为$values
期望您的值是这样的。如果您仍想将它们作为值传递到函数中,可以添加它,然后将mysqli_stmt_bind_param
传递到$ref_values
函数中:
call_user_func_array()
现在我们开始使用foreach ($values as &$value) $ref_values[] = &$value;
,这也是不正确的。参考PHP手册:
内爆(返回值):
返回一个字符串,其中包含相同顺序的所有数组元素的字符串表示形式,每个元素之间都有粘合字符串。
mysqli_stmt_bind_result(第二个参数):
要绑定的变量。
所以你在这里尝试做的是让implode()
由string
变量返回,这没有任何意义。幸运的是,mysqli_stmt_get_result()
返回一个在执行后获取的对象,这意味着不需要bind_result函数。所以尝试删除该行。
总而言之,我会重写这样的代码:
implode()
对我而言,这听起来应该有效,但如果它不起作用,请告诉我(我可能会遗漏某些东西或在某处错误思考)。
要回答第二个问题,这一切看起来都不错,除非我不建议您使用function secure_sql($query, $values, $datatypes) {
global $link;
$stmt = mysqli_prepare($link, $query);
foreach ($values as &$value) $ref_values[] = &$value;
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $ref_values));
mysqli_stmt_execute($stmt);
return mysqli_stmt_get_result($stmt);
}
- 函数,因为它们最终会被删除(如PHP-manual中所述)。如果您计划使用对象,那么大多数对象在我的更改时都是相似的(除了您需要使用对象属性和对象方法而不是...),除了mysqli_
功能。幸运的是,通过将数组指定为第一个参数,包括对象和方法名称(call_user_func_array()
),也可以使用它调用方法。
编辑:考虑到它的必要性,我创建了一个执行相同操作的函数,但改为使用mysqli对象:
call_user_func_array($prepared_obj, "bind_param") ...)