如何在我的查询中不使用原始字段名称?

How not to use raw field names in my query?

我正在研究 PDO 并尝试更简单地使用插入和更新查询。我之前的问题得到了很好的答案,但他们也指出我的查询中存在严重的注入问题。我试过自己弄清楚,但并不像我想的那样顺利。我怎样才能消除这些查询中的注入问题?请帮我!

function pdoSet($fields, &$values, $source = array()){
$set = '';
$values = array();
if(!$source) $source = &$_POST;
foreach($fields as $field){
    if(isset($source[$field])){
        $set .= " $field =:$field, ";
        $values[$field] = $source[$field];
    }
}
return substr($set, 0, -2);
}

$fields = array(
'name'
, 'part'
, 'tel'
, 'email'
, 'title'
, 'contents'
);

if(!$idx){
    $fields[] = 'reg_date';
    $values[] = 'now()';
    $st = $pdo -> prepare("insert into qna_board set ".pdoSet($fields,   $values));
    $st->execute($values);
}else{
    $st = $pdo -> prepare("update qna_board set ".pdoSet($fields, $values)."   where idx = :idx");
    $st ->bindParam(":idx", $idx);
    $st->execute($values + compact('idx'));
}

起初您的代码看起来确实容易 SQL 注入(正如 Deceze 在 中指出的那样),但经过仔细检查后我非常确定它是安全的。

看起来麻烦的部分是:

foreach($fields as $field){
    if(isset($source[$field])){
        $set .= " $field =:$field, ";
        $values[$field] = $source[$field];
    }
}

您正在动态生成 column => placeholder 对,看起来这些值来自 $_POST 超全局,但它们不是。您传递给 pdoSet() 的第一个参数始终是一个硬编码数组,而不是基于保护您免受注入的用户输入。

附带说明一下,我会稍微重新考虑一下您的代码设计。你说你只是在学习 PDO(我为之鼓掌,它是一个很棒的驱动程序),所以这里有一个非常简单的 OOP 片段,它更清晰一些,你可以以此为基础:

class FooBarBaz
{
    /**
     * @var PDO
     */
    protected $pdo;

    /**
     * @param    string   $index
     * @param    array    $input_values
     * @return   bool
     */
    public function update($index = null, Array $input_values = null)
    {
        if (is_null($input_values))
        {
            $input_values = $_POST;
        }

        $column_list = [
            'name',
            'part',
            'tel',        
            'email',        
            'title',        
            'contents',
        ];

        $placeholders = [];
        $query_values = [];

        foreach ($column_list as $column_name)
        {
            if (array_key_exists($column_name, $input_values))
            {
                $placeholders[] = sprintf('`%s` = :%s', $column_name, $column_name);
                $query_values[$column_name] = $input_values[$column_name];
            }
        }

        if (isset($index))
        {
            $sql = sprintf('UPDATE qna_board SET %s WHERE idx = :idx', implode(',', $placeholders));
            $query_values['idx'] = $index;
        }
        else
        {
            $sql = sprintf('INSERT INTO qna_board SET %s, reg_date = NOW()', implode(',', $placeholders));
        }

        return $this->pdo->prepare($sql)->execute($query_values);
    }
}

也不是我按字面意思指定 SQL 函数 NOW() 的方式。这是因为这样的数据必须在解析时传递给 PDO (see this answer)。请注意,您还应该检查 errors/exceptions(您应该配置 PDO 以使用后者)并适当地处理它们。一个好的 DBAL 也会从程序员那里抽象出很多 "guts" PDO,这通常是一件好事(当然,一旦你练习了基础知识)。