Php PDO/prep语句/whitelisting/set字符集,是否足够安全以防止注入

Php PDO/prep语句/whitelisting/set字符集,是否足够安全以防止注入,php,mysql,pdo,sql-injection,Php,Mysql,Pdo,Sql Injection,我正在从扩展mysql转换到PDO,在阅读了SO和其他地方的大师们的所有资料后,我还有一些疑问。我提出了以下方法来解决典型查询的sql注入问题。我只是想知道这是否足够,或者在我将其复制到所有应用程序之前,我对白名单有点过火了 我不清楚我是否正确地列出了白名单,也就是说,我是否应该以某种方式逃脱 此外,我也不确定是应该为每个查询将attribute simulate设置为false,还是只为脚本设置一次 $link = new PDO("mysql:host=$hostname;dbname=$d

我正在从扩展mysql转换到PDO,在阅读了SO和其他地方的大师们的所有资料后,我还有一些疑问。我提出了以下方法来解决典型查询的sql注入问题。我只是想知道这是否足够,或者在我将其复制到所有应用程序之前,我对白名单有点过火了

我不清楚我是否正确地列出了白名单,也就是说,我是否应该以某种方式逃脱

此外,我也不确定是应该为每个查询将attribute simulate设置为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();}
$link=newpdo(“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=数组(“价格”、“版本”、“型号”);
$allowedTables=数组(“价格”、“版本”、“型号”);
$field=array('model_id','version_id','price','models.active','versions.active');
$allowedFields=数组('model_id','version_id','price','models.active','versions.active');
如果(计数(数组差异($field,$allowedFields))=0,计数(数组差异($table,$allowedTables))=0){
$sql=“选择计数(不同的“.”$field[0]”作为“.”$table[0]”中的ctmod`
内部联接“`.$table[1]”.`使用(`.$field[1]”.“`)
内部联接“`.$table[2]”。使用(`.$field[0].`)
其中“`.”$field[2]。“`BETWEEN:arr_i和:arr_k
和“$field[3]”=:m_act
以及“$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);
对于($i=0;$i<$ctpri;$i++){
$k=$i+1;
$arr_i=$arr_pri[$i]+1;
$arr_k=$arr_pri[$k];
$stmt->execute();
而($r=$stmt->fetch(PDO::fetch\U 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";
    
  • 另一种选择是,您可以使用here文档,这样就不必担心字符串的定界问题。如果字符串中有文字双引号,这很好,因为您不必转义它们:

    $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;
    
    (该示例使用PHP5.4语法取消对函数返回的数组的引用。)

    或者你可以,这样你就不用检查了

    $link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    

我怀疑你在白名单上确实有点过火了。不仅是白名单,甚至还有准备好的声明。为了满足你的错误观点,你过度设计了你的查询,使其变得完全无法理解

您需要了解的是,任何常量值在设计上都是安全的。因此,使用、白名单或准备好的声明都毫无意义

因此,与其

AND " . $field[3] . " = :m_act
你应该直接写

AND versions.active = 'Y'
无任何绑定或白名单


您只需要保护动态值。因此,您只能对$arr_i和$arr_k使用准备好的语句。所有其他查询部分都必须直接写入查询,就像您以前做的那样。

谢谢,我现在感觉好多了。几点意见:您需要去掉AND子句的反引号。出于某种原因,可能是因为字符串有一个点(models.active),我认为,它不能与它们一起工作。我很感激你告诉我错误处理的方法,我有很多东西要学。其中之一是我将在哪里找到PDO/PHP引发的异常/错误。@Bernard,您应该分别分隔表名和列名:
`models`.`active`
。尽管划界是没有必要的,因为它们不是,也不包含特殊字符或空格。谢谢。那很好。当我想到这一点时,像这样阅读查询是相当混乱的。在数组中使用带有表/字段名称的键可能更好。@YourCommonSense,我在回答的第一行回答了OP的问题。考虑到您多次对良好的编码实践提出切题的建议,我对您的评论感到惊讶。@YourCommonSense,另一个想法是:使安全SQL的格式更方便有利于安全性,因为开发人员希望保持高效。如果我们向他们展示如何安全高效,他们更有可能接受最佳编码实践。@YCS,如果我知道一切都是正确的,我就不会一开始就提出这个问题。我感谢你的意见,但肯定不是你的语气。如果你想保持同样的语气,如果你以后不再回答我的任何问题,我将不胜感激。事实上,我正在学习并不意味着我会
AND versions.active = 'Y'