我正在尝试将这些mySQl INSERT INTO和Update语句切换到PDO预处理语句(主要是为了防止SQL注入),但是我在使语法正确方面遇到了一些困难。
我目前正在使用两种类型的INSERT / Update语句:
声明1 - 名称是硬编码的
$qry = "INSERT INTO customer_info(fname, lname, email, user_name, password)
VALUES('$_POST[fname]','$_POST[lname]','$_POST[email]','$user_name','".sha1($salt + $_POST['password'])."')";
$result = @mysql_query($qry)
声明2 - 动态添加名称
不是列出每个元素的名称,而是动态添加大多数名称(名称引用为$ fieldlist或$ setlist,值为$ vallist)。唯一的硬编码名称/值是user_id或者是数组。我在下面列出了完整的代码。
$result = mysql_query('UPDATE fit_table SET '.$setlist.' WHERE user_id='.$user_id);
if (mysql_affected_rows()==0) {
$result = mysql_query('INSERT INTO fit_table ('.$fieldlist.') VALUES ('.$vallist.')'); };
这就是我尝试过的:
声明1 - 基于此帖https://stackoverflow.com/a/60530/1056713
$stmt = $conn->prepare("INSERT INTO customer_info VALUES(:fname, :lname, :email, :user_name, :password)");
$stmt->bindValue(':fname', $fname);
$stmt->bindValue(':lname', $lname);
$stmt->bindValue(':email', $email);
$stmt->bindValue(':user_name', $user_name);
$stmt->bindValue(':password ', $password);
$stmt->execute();
声明2 - 基于此PDO包装器https://github.com/Xeoncross/DByte/blob/master/DB.php(在此帖https://stackoverflow.com/a/12500462/1056713中引用)
static function insert($fit_table, array $fieldlist){
$query = "INSERT INTO`$fit_table`(`" . implode('`,`', array_keys('.$fieldlist.')). '`)
VALUES(' . rtrim(str_repeat('?,', count($fieldlist = array_values('.$vallist.'))), ',') . ')';
return DB::$p
? DB::column($query . 'RETURNING` user_id `', $fieldlist)
: (DB::query($query, $fieldlist) ? static::$c->lastInsertId() : NULL);
}
Statement 2 的完整代码(这就是目前动态添加名称的方式)
// INSERT
$fieldlist=$vallist='';
foreach ($_POST as $key => $value) {
if ($key=='pants_waistband'){$value= implode(',',$value);}
$fieldlist.=$key.',';
$vallist.='\''.($value).'\',';
}
$fieldlist=substr($fieldlist, 0, -1);
$vallist=substr($vallist, 0, -1);
$fieldlist.=', user_id';
$vallist.=','.$user_id;
// UPDATE
$setlist='';
foreach ($_POST as $key => $value) {
if ($key=='pants_waistband'){$value= implode(',',$value);}
$setlist.=$key .'=\''.$value.'\',';
}
$setlist=substr($setlist, 0, -1);
$result = mysql_query('UPDATE fit_table SET '.$setlist.' WHERE user_id='.$user_id);
if (mysql_affected_rows()==0) {
$result = mysql_query('INSERT INTO fit_table ('.$fieldlist.') VALUES ('.$vallist.')');}
答案 0 :(得分:4)
看,白名单并不像看起来那么无聊! 动态查询很棒,没有理由放弃这个想法 至少你可以使它半动态,以避免所有这些重复。
PDO有一个很棒的功能 - 它可以接受带有值的数组,不需要重复绑定。 它可以像
一样简单$stmt = $conn->prepare('INSERT INTO customer_info VALUES(?,?,?,?,?)');
$stmt->execute($_POST);
如果$ _POST包含正确顺序的字段数,它将被执行。 但是一旦我们在查询中需要字段名称,它将丢失所有自动化(如在您自己的答案中)或变得不安全(如在您之前的动态代码中)。
好吧,让它既安全又动态
你唯一需要的是一个允许字段名称的数组,它将是我们的白名单
然后你可以使用这个数组循环$ _POST,动态创建查询
这是一个自动化过程的功能:
它需要三个参数,但实际上只使用一个
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);
}
它将返回看起来像
的字符串`field1`=?,`field2`=?,`field3`=?
并将填充$values
数组以用于PDO查询。
请注意,Mysql允许INSERT和UPDATE查询的SET语法 - 不需要VALUES语法。所以,两种功能都有。
对于插入,它就像
一样简单$fields = array("fname", "lname", "email", "user_name");
$stmt = $dbh->prepare("UPDATE users SET ".pdoSet($fields,$values));
$stmt->execute($values);
对于任何数量的领域,它都将保持相同的3-liner!
对于更新,它需要更长的代码。我们需要为查询添加一些条件,以及向$ values数组添加另一个成员。
$fields = array("fname", "lname", "email", "user_name");
$stmt = $dbh->prepare("UPDATE users SET ".pdoSet($fields,$values)." WHERE id = :id");
$values["id"] = $_POST['id'];
$stmt->execute($values);
唯一的问题仍然是如何添加不在$ _POST数组中的自定义字段 在准备之前,我只是直接将它们添加到那里:
$_POST['password'] = sha1($_POST['email'].$_POST['password']);
希望这是你要求的。
只需澄清一件事 准备好的陈述不足以阻止注射,你的案例就是一个很好的例子。他们只处理数据,但保护字段名称是你的负担。 然而,你使用的旧mysql方式没有任何问题。您的代码只缺少相同的白名单(当然还有正确的数据格式)。但如果添加,它将使您的mysql查询像PDO一样安全。
答案 1 :(得分:0)
我发现白色列表名称比其他方法更安全(感谢几个人的帮助,包括@zerkms),并希望分享完成的语句。
它现在正常工作,并包含使用PDO连接数据库所需的方法。我还切换了语句中使用的db用户帐户,因为我了解到最好使用具有有限权限的帐户(只能SELECT,INSERT和UPDATE)来最小化黑客可能造成的损害。
try {
$conn = new PDO('mysql:host=localhost;dbname=dbname', 'Username', 'MyPassword');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare('INSERT INTO customer_info (fname...) VALUES(:fname...)');
$stmt->bindParam(':fname', $_POST['fname'], PDO::PARAM_STR);
$stmt->execute();
} catch(PDOException $e) {
echo $e->getMessage();
}