4

これらの mySQl INSERT INTO および Update ステートメントを PDO 準備済みステートメントに切り替えようとしています (主に SQL インジェクションを防ぐため) が、構文を正しくするのに苦労しています。

現在、2 種類の 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);
}

ステートメント 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.')');}  
4

2 に答える 2

4

ほら、ホワイトリストは見た目ほど退屈ではありません!
動的クエリは優れており、このアイデアを放棄する理由はありません。
少なくとも、これらすべての繰り返しを避けるために、 それをsemi-dynamicにすることができます。

PDO には 1 つの優れた点があります。値を持つ配列を受け入れることができるため、バインディングを繰り返す必要がありません。次のように簡単にすることができます

$stmt = $conn->prepare('INSERT INTO customer_info VALUES(?,?,?,?,?)');
$stmt->execute($_POST);

$_POST に正確な数のフィールドが正しい順序で含まれている場合に実行されます。ただし、クエリでフィールド名が必要になるとすぐに、すべての自動化が失われるか (自分の回答のように)、安全ではなくなります (以前の動的コードのように)。

それでは、安全かつ動的にしましょう。
必要なのは、ホワイト リストとなる許可されたフィールド名の配列だけです。
次に、この配列を使用して $_POST をループし、クエリを動的に作成できます。
プロセスを自動化する関数は次のとおり
です。3 つの引数を取りますが、実際には 1 つしか使用しません。

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`=?

$valuesPDOクエリで使用するために配列にデータを入力します。

Mysql では、INSERT クエリと UPDATE クエリのどちらでも SET 構文が使用できることに注意してください。VALUES 構文は必要ありません。したがって、1 つの関数で両方の種類を処理できます。

挿入の場合は、次のように簡単です

$fields = array("fname", "lname", "email", "user_name");
$stmt = $dbh->prepare("UPDATE users SET ".pdoSet($fields,$values));
$stmt->execute($values);

そして、フィールドの数に関係なく、同じ 3 ライナーのままになります。

更新には、少し長いコードが必要です。$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']);

これがあなたが求めていたものであることを願っています。

明確にすることは1つだけです。
準備されたステートメントは注射を止めるのに十分ではなく、あなたのケースは完璧な例です. それらはデータのみを扱いますが、フィールド名を保護することはあなたの負担です。それでも、使用していた古い mysql の方法に問題はありません。あなたのコードには、同じホワイトリストがありません (もちろん、適切なデータ形式も)。ただし、追加すると、mysql クエリが PDO と同じくらい安全になります。

于 2012-12-12T20:08:18.160 に答える
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();
}
于 2012-12-12T06:02:10.543 に答える