我正在从扩展mysql转换为PDO,在阅读了所有我可以从SO和其他地方的大师那里,我有一些残余的怀疑。我想出了以下内容来解决典型查询的sql注入问题。我只是想知道这是否足够或可能是我在白名单上有点过分,然后我将其复制到我的所有应用程序中。
我不清楚我是否正确地进行了白名单,即如果我也应该以某种方式逃脱。
另外,我不确定是否应该为每个查询将setAttribute模拟为false,或者只为脚本执行一次。
$link = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);
$link->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$arr_i=$arr_k='';
$m_act=$v_act='Y';
$table = array('prices', 'versions', 'models');
$allowedTables = array('prices', 'versions', 'models');
$field = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
$allowedFields = array('model_id', 'version_id', 'price', 'models.active', 'versions.active');
if(count( array_diff($field, $allowedFields))==0 AND count( array_diff($table, $allowedTables))==0){
$sql = "SELECT COUNT(DISTINCT `" . $field[0] . "`) as ctmod FROM `" . $table[0] . "`
INNER JOIN `" . $table[1] . "` USING (`" . $field[1] . "`)
INNER JOIN `" . $table[2] . "` USING (`" . $field[0] . "`)
WHERE `" . $field[2] . "` BETWEEN :arr_i AND :arr_k
AND " . $field[3] . " = :m_act
AND " . $field[4] . " = :v_act";
$stmt = $link->prepare($sql);
$stmt->bindParam(':arr_i', $arr_i, PDO::PARAM_INT);
$stmt->bindParam(':arr_k', $arr_k, PDO::PARAM_INT);
$stmt->bindParam(':m_act', $m_act, PDO::PARAM_STR);
$stmt->bindParam(':v_act', $v_act, PDO::PARAM_STR);
for ($i=0; $i < $ctpri; $i++){
$k=$i+1;
$arr_i=$arr_pri[$i]+1;
$arr_k=$arr_pri[$k];
$stmt->execute();
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
$ctmod[] = $r['ctmod'];
}
}
}
else{die();}
答案 0 :(得分:4)
我怀疑你确实对白名单有点过分了。而且不仅有白名单,甚至还有准备好的陈述。为了满足您的错误观点,您将查询过度设计到完全无法理解的混乱状态。
您需要了解的是任何常量值都是设计安全的。因此,对它使用或列入白名单也没有准备好的陈述是完全没有意义的。
所以,而不是
AND " . $field[3] . " = :m_act
你应该写
AND versions.active = 'Y'
没有任何绑定或白名单。
您需要保护的只是动态值。因此,您必须仅为$ arr_i和$ arr_k使用预准备语句。所有其他查询部分必须直接写入查询,就像之前一样。
答案 1 :(得分:3)
是的,您的代码完全不受SQL注入的影响。干得好。
尽管正如@YourCommonSense指出的那样,在您展示的示例中没有理由将表和列名称变为变量。将它们直接写入查询会更简单。
因此,我假设您提出这个问题,因为您做有时会通过应用程序逻辑或变量选择表名和列名,即使您没有在此特定示例中显示它。
我提供的唯一提示是:
所有字符串连接,结尾双引号,使用.
并重新启动双引号会使代码看起来不整齐,并且可能会混淆以跟踪哪些双引号我开始并停止了。另一种变量的PHP字符串插值样式是用大括号括起来,如下所示:
$sql = "SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
AND `{$field[3]}` = :m_act
AND `{$field[4]}` = :v_act";
对于另一种选择,您可以在这里使用文档,因此您根本不必担心分隔字符串。如果你的字符串中有双引号,那就太好了,因为你不必转义它们:
$sql = <<<GO
SELECT COUNT(DISTINCT `{$field[0]}`) as ctmod FROM `{$table[0]}`
INNER JOIN `{$table[1]}` USING (`{$field[1]}`)
INNER JOIN `{$table[2]}` USING (`{$field[0]}`)
WHERE `{$field[2]}` BETWEEN :arr_i AND :arr_k
AND `{$field[3]}` = :m_act
AND `{$field[4]}` = :v_act
GO;
最后,它与SQL注入无关,但一个好的做法是检查prepare()
和execute()
的返回值,因为它们返回 false 如果在解析或执行时发生错误。
if (($stmt = $link->prepare($sql)) === false) {
trigger_error(PDO::errorInfo()[2], E_USER_ERROR);
}
(该示例使用PHP 5.4语法取消引用从函数返回的数组。)
否则你可以configure PDO to throw exceptions,所以你不必检查。
$link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);