【问题标题】:Are Dynamic Prepared Statements Bad? (with php + mysqli)动态准备语句不好吗? (用 php + mysqli)
【发布时间】:2010-09-17 02:51:35
【问题描述】:

我喜欢动态 SQL 的灵活性,我喜欢预处理语句的安全性 + 改进的性能。所以我真正想要的是动态准备语句,这很麻烦,因为 bind_param 和 bind_result 接受“固定”数量的参数。所以我使用 eval() 语句来解决这个问题。但我觉得这是个坏主意。这是我的意思的示例代码

// array of WHERE conditions
$param = array('customer_id'=>1, 'qty'=>'2');
$stmt = $mysqli->stmt_init();

$types = ''; $bindParam = array(); $where = ''; $count = 0;

// build the dynamic sql and param bind conditions
foreach($param as $key=>$val)
{
    $types .= 'i';
    $bindParam[] = '$p'.$count.'=$param["'.$key.'"]'; 
    $where .= "$key = ? AND ";
    $count++;
}

// prepare the query -- SELECT * FROM t1 WHERE customer_id = ? AND qty = ?
$sql = "SELECT * FROM t1 WHERE ".substr($where, 0, strlen($where)-4);
$stmt->prepare($sql);

// assemble the bind_param command
$command = '$stmt->bind_param($types, '.implode(', ', $bindParam).');';

// evaluate the command -- $stmt->bind_param($types,$p0=$param["customer_id"],$p1=$param["qty"]);
eval($command);

最后一个 eval() 语句是个坏主意吗?我试图通过将值封装在变量名 $param 后面来避免代码注入。

有人有意见或其他建议吗?有没有我需要注意的问题?

【问题讨论】:

  • 您可能需要稍微改一下您的问题标题,以便其他人可以更轻松地找到您的问题。

标签: php sql dynamic eval prepared-statement


【解决方案1】:

我认为在这里使用eval() 很危险。

试试这个:

  • 迭代params数组以构建带有问号"SELECT * FROM t1 WHERE p1 = ? AND p2 = ?"的SQL字符串
  • 请致电prepare()
  • 使用call_user_func_array() 调用bind_param(),传入动态参数数组。

代码:

call_user_func_array(array($stmt, 'bind_param'), array($types)+$param);

【讨论】:

  • 小心array($types)+$param。如果 $param 是索引为 0 的数组,则 $param 中的值将被删除以支持 $types,并且您将收到错误,因为变量少于字符串中指示的变量。 php.net/manual/en/language.operators.array.php
【解决方案2】:

我做了一个过滤函数,它接收一个数组,一个关联数组,比如 $_GET:

在模型类中,我定义了几个属性,包括架构:

private $table_name = "products";

protected $schema = [
    'id' => 'INT',
    'name' => 'STR',
    'description' => 'STR',
    'size' => 'STR',
    'cost' => 'INT',
    'active' => 'BOOL'
];

然后是一个过滤器方法,它接收一个关联的条件数组:

function filter($conditions)
{
    $vars   = array_keys($conditions);
    $values = array_values($conditions);

    $where = '';
    foreach($vars as $ix => $var){
        $where .= "$var = :$var AND ";
    }
    $where =trim(substr($where, 0, strrpos( $where, 'AND ')));

    $q  = "SELECT * FROM {$this->table_name} WHERE $where";
    $st = $this->conn->prepare($q);

    foreach($values as $ix => $val){
        $st->bindValue(":{$vars[$ix]}", $val, constant("PDO::PARAM_{$this->schema[$vars[$ix]]}"));
    }

    $st->execute();
    return $st->fetchAll(PDO::FETCH_ASSOC);
}

并且非常适合过滤结果

【讨论】:

    【解决方案3】:

    你并不需要准备好的语句和绑定的参数,因为你总是可以使用 mysql_real_escape_string()。你是对的;动态生成的 SQL 更加灵活和有价值。

    这是一个使用常规 mysql_* 接口的简单示例:

    // Array of WHERE conditions
    $conds = array("customer_id" => 1, "qty" => 2);
    
    $wherec = array("1");
    foreach ($conds as $col=>$val) $wherec[] = sprintf("`%s` = '%s'", $col, mysql_real_escape_string($val));
    
    $result_set = mysql_query("SELECT * FROM t1 WHERE " . implode(" AND ", $wherec);
    

    当然,这是一个简单的示例,要使其有用,您必须对其进行大量构建和改进,但它展示了想法,并且真的非常非常有用。例如,这是一个完全通用的函数,用于向任意表中插入新行,列中填充了关联数组中的值,并且完全安全 SQL 注入:

    function insert($table, $record) {
        $cols = array();
        $vals = array();
        foreach (array_keys($record) as $col) $cols[] = sprintf("`%s`", $col);
        foreach (array_values($record) as $val) $vals[] = sprintf("'%s'", mysql_real_escape_string($val));
    
        mysql_query(sprintf("INSERT INTO `%s`(%s) VALUES(%s)", $table, implode(", ", $cols), implode(", ", $vals)));
    }
    
    // Use as follows:
    insert("customer", array("customer_id" => 15, "qty" => 86));
    

    【讨论】:

    • 绑定参数在转义之外增加了额外的安全性。使用它们是一个好习惯。
    • 您能告诉我他们提供哪些额外的安全性吗?与我的解决方案相比,绑定参数可以保护您免受哪种攻击?因为如果你指的是程序员犯错的能力,我宁愿拥有更强大的工具而不是约束工具。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    相关资源
    最近更新 更多