我正在从扩展
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','models'); $field = array('model_id','version_id','price','models.active','versions.active'); $allowedFields = array('model_id','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();}
解决方法
是的,您的代码完全不受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); }
或者你可以configure PDO to throw exceptions,所以你不必检查.
$link->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);