PDO / prep语句/白名单/设置字符集,足够安全,可以防止注入

时间:2013-12-21 20:50:25

标签: php mysql pdo sql-injection

我正在从扩展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();}

2 个答案:

答案 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);