複数のクエリに対するPDOサポート(PDO_MYSQL、PDO_MYSQLND)


102

PDOが1つのステートメントで実行される複数のクエリをサポートしていないことは知っています。私はGoogleを利用していて、PDO_MYSQLとPDO_MYSQLNDについて話している投稿をいくつか見つけました。

PDO_MySQLは、他の従来のMySQLアプリケーションよりも危険なアプリケーションです。従来のMySQLでは、単一のSQLクエリしか許可されていません。PDO_MySQLにはそのような制限はありませんが、複数のクエリが挿入されるリスクがあります。

From:PDOおよびZend Frameworkを使用したSQLインジェクションからの保護(2010年6月、Julianによる)

PDO_MYSQLとPDO_MYSQLNDは複数のクエリをサポートしているようですが、それ以上の情報は見つかりません。これらのプロジェクトは中止されましたか?PDOを使用して複数のクエリを実行する方法はありますか?


4
SQLトランザクションを使用します。
テレシュコ2013年

複数のクエリを使用する理由は何ですか?それらは処理されず、次々に実行するのと同じです。私見の長所はなく、短所だけです。SQLInjectionの場合、攻撃者が望むことは何でもできるようにします。
mleko 2014

それは現在2020年であり、PDOはこれをサポートしています-下の私の答えを見てください。
アンドリス

回答:


141

私が知っているように、PHP 5.3 でPDO_MYSQLND置き換えられPDO_MYSQLました。混乱している部分は、名前がまだ残っていることPDO_MYSQLです。したがって、NDはMySQL + PDOのデフォルトのドライバーです。

全体として、一度に複数のクエリを実行するには、次のものが必要です。

  • PHP 5.3以降
  • mysqlnd
  • エミュレートされた準備済みステートメント。PDO::ATTR_EMULATE_PREPARES1(デフォルト)に設定されていることを確認します。または、準備済みステートメントの使用を避け、$pdo->exec直接使用することもできます。

execの使用

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works regardless of statements emulation
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$db->exec($sql);

ステートメントの使用

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works not with the following set to 0. You can comment this line as 1 is default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$stmt = $db->prepare($sql);
$stmt->execute();

注意:

エミュレートされた準備済みステートメントを使用する場合は、DSN(5.3.6以降で利用可能)で適切なエンコーディング(実際のデータエンコーディングを反映する)を設定していることを確認してください。それ以外の場合、奇妙なエンコーディングが使用されていると、SQLインジェクションが発生する可能性があります


37
答え自体に問題はありません。複数のクエリを実行する方法について説明します。答えに欠陥があるというあなたの仮定は、クエリにユーザー入力が含まれているという仮定から来ています。複数のクエリを一度に送信するとパフォーマンスが向上する有効なユースケースがあります。この質問に対する代替の回答として手順を使用することを提案できますが、それはこの回答を悪くしません。
Gajus 14

9
この回答のコードは悪いものであり、いくつかの非常に有害な慣行を促進します(準備ステートメントのエミュレーションの使用により、コードがSQLインジェクションの脆弱性に対して開かれます)。使用しないでください。
tereško

17
この答え、特にエミュレーションモードに問題はありません。これはデフォルトでpdo_mysqlで有効になっており、問題があった場合、すでに何千回もの注入が行われます。しかし、まだ誰も近づいていません。だからそうなるのです。
常識14

3
実際、なんとか感情だけでなくいくつかの議論を提供できたのはイルクマクセルだけだった。しかし、彼がもたらしたリンクはまったく無関係です。「PDOは常にこのバグの影響を受けないと明記されているため、最初のものはまったく適用できません。2つ目は、適切なエンコーディングを設定することで簡単に解決できます。したがって、警告ではなく、注意を引くだけの価値があります。
常識14

6
私たちの開発者だけが書いたSQLを使用する移行ツールを書いている人と言えば(つまり、SQLインジェクションは問題ではありません)、これは私を大いに助け、このコードが有害であることを示すコメントはすべてを完全には理解していませんその使用法のコンテキスト。
ルーク

17

半日のこれをいじった後、PDOにバグがあることがわかりました...

-

//This would run as expected:
$pdo->exec("valid-stmt1; valid-stmt2;");

-

//This would error out, as expected:
$pdo->exec("non-sense; valid-stmt1;");

-

//Here is the bug:
$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");

それはを実行し"valid-stmt1;"、停止し"non-sense;"、エラーをスローしません。を実行せず"valid-stmt3;"、trueを返し、すべてが正常に実行されたことを嘘にします。

私はそれがエラーになることを期待します"non-sense;"が、そうではありません。

ここで私はこの情報を見つけました: 無効なPDOクエリはエラーを返しません

これがバグです:https : //bugs.php.net/bug.php?id=61613


ですから、mysqliでこれを試してみましたが、どのように機能するかについて確かな答えを見つけられなかったので、使用したい人のためにここに置いておきます。

try{
    // db connection
    $mysqli = new mysqli("host", "user" , "password", "database");
    if($mysqli->connect_errno){
        throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error );
        exit();
    }

    // read file.
    // This file has multiple sql statements.
    $file_sql = file_get_contents("filename.sql");

    if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){
        throw new Exception("File is empty. I wont run it..");
    }

    //run the sql file contents through the mysqli's multi_query function.
    // here is where it gets complicated...
    // if the first query has errors, here is where you get it.
    $sqlFileResult = $mysqli->multi_query($file_sql);
    // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements.

    $sqlCount = 1;
    if( $sqlFileResult == false ){
        throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }");
    }

    // so handle the errors on the subsequent statements like this.
    // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line
    while($mysqli->more_results()){
        $sqlCount++;
        // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info.
        if($mysqli->next_result() == false){
            throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }");
        }
    }
}
catch(Exception $e){
    echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>";
}

$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");前の2つのexecなしで実行した場合にのみ機能しますか?途中でエラースローするようにすることはできますが、execが成功したに実行するとエラーがスローされます。
Jeff Puckett 2016年

いいえ、そうではありません。それがPDOのバグです。
Sai Phaninder Reddy J 2016

1
私の悪いことに、これら3つ$pdo->exec("")は互いに独立しています。今度はそれらを分割して、問題が発生するために順番に並んでいる必要がないことを示します。これら3つは、1つのexecステートメントで複数のクエリを実行する3つの構成です。
Sai Phaninder Reddy J 2016

面白い。私の投稿された質問を見る機会がありましたか?これがexecページにのみある場合はエラーがスローされるため、これは部分的にパッチされているのかと思いますが、exec複数のSQLステートメントをそれぞれに複数実行すると、同じバグが再現されます。しかし、それexecがページにのみある場合、それを再現することはできません。
Jeff Puckett

execページの1つに複数のステートメントがありましたか?
Sai Phaninder Reddy J 2016

3

迅速かつ汚いアプローチ:

function exec_sql_from_file($path, PDO $pdo) {
    if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m))
        return;

    foreach ($m[0] as $sql) {
        if (strlen(trim($sql)))
            $pdo->exec($sql);
    }
}

適切なSQLステートメントのエンドポイントで分割します。エラーチェック、注入保護はありません。ご使用前にご理解の上ご利用ください。個人的には、統合テストの生の移行ファイルをシードするために使用します。


1
SQLファイルにmysql組み込みコマンドが含まれている場合、これは失敗します... SQLファイルが大きい場合、PHPメモリの制限もおそらく打たれます... ;SQLにプロシージャまたはトリガー定義が含まれている場合、ブレークで分割します...多くのそれが良くない理由
Bill Karwin、2016年

1

何千人もの人々のように、私はこの質問を探しています:
複数のクエリを同時に実行でき、1つのエラーが発生しても実行
されませんでした。私の問題
したがって、私はSQLインジェクションで問題なく機能する関数を作成しました。
同様の質問を探している人には役立つかもしれないので、ここに置いて使用します

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

使用する(例):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

そして私のつながり:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

注:
このソリューションは、複数のステートメントを一緒に実行するのに役立ちます。
誤ったステートメントが発生した場合、他のステートメントは実行されません


0

次のコードを試しました

 $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

その後

 try {
 $db->query('SET NAMES gbk');
 $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1');
 $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
 }
 catch (PDOException $e){
 echo "DataBase Errorz: " .$e->getMessage() .'<br>';
 }
 catch (Exception $e) {
 echo "General Errorz: ".$e->getMessage() .'<br>';
 }

そして得た

DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/*' LIMIT 1' at line 1

$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);後に追加した場合$db = ...

その後、空白のページを取得しました

代わりにSELECT試した場合DELETE、どちらの場合も次のようなエラーが発生しました

 DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1

だから注射は不可能だという私の結論...


3
あなたはそれをこれに言及する場合、それを新しい質問にすべき
常識14

私が試した結果、あまり質問はありません。そして私の結論。最初の質問は古く、おそらく現時点では実際ではありません。
Andris 14

これが問題のどの部分にどのように関連しているかはわかりません。
cHao

問題は言葉ですbut you risk to be injected with multiple queries.私の答えは注射についてです
Andris

0

この機能を試してください:mltipleクエリと複数の値の挿入。

function employmentStatus($Status) {
$pdo = PDO2::getInstance();

$sql_parts = array(); 
for($i=0; $i<count($Status); $i++){
    $sql_parts[] = "(:userID, :val$i)";
}

$requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts));
$requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT);
for($i=0; $i<count($Status); $i++){
    $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR);
}
if ($requete->execute()) {
    return true;
}
return $requete->errorInfo();
}

0

PDOはこれをサポートしています(2020現在)。通常どおりPDOオブジェクトでquery()を呼び出し、クエリをで区切ります。複数ある場合は、nextRowset()で次のSELECT結果に進みます。結果セットはクエリと同じ順序になります。明らかにセキュリティへの影響について考えてください。ユーザーが指定したクエリを受け入れたり、パラメーターを使用したりしないでください。たとえば、コードで生成されたクエリで使用します。

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

このような理由を理解することは決してありません。「ここに、推奨されるすべての推奨プラクティスを無視したセキュリティの大きな穴があるコードがあるので、セキュリティへの影響について考える必要があります。」誰がそれについて考えるべきですか?彼らが考えるべき時-このコードを使用する前、またはハッキングされた後?この関数を書いたり、他の人に提供したりする前に、まずそれについて考えてみませんか?
常識

親愛なる@YourCommonSenseが一度に複数のクエリを実行すると、パフォーマンスが向上し、ネットワークトラフィックが減少し、サーバーは関連するクエリを最適化できます。私の(単純化した)例は、それを使用するために必要なメソッドを紹介することのみを目的としています。あなたが言及しているそれらの良い習慣を使用しない場合にのみ、それはセキュリティホールです。ところで、私は「私は決して理解できないだろう...」という人が簡単にできるときに不審です... :-)
Andris
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.