PDO-Unterstützung für mehrere Abfragen (PDO_MYSQL, PDO_MYSQLND)

102

Ich weiß, dass PDO nicht mehrere Abfragen unterstützt, die in einer Anweisung ausgeführt werden. Ich habe googelt und einige Beiträge gefunden, die über PDO_MYSQL und PDO_MYSQLND sprechen.

PDO_MySQL ist eine gefährlichere Anwendung als alle anderen herkömmlichen MySQL-Anwendungen. Herkömmliches MySQL erlaubt nur eine einzige SQL-Abfrage. In PDO_MySQL gibt es keine solche Einschränkung, aber Sie riskieren, mit mehreren Abfragen injiziert zu werden.

Von: Schutz vor SQL-Injection mit PDO und Zend Framework (Juni 2010; von Julian)

Es scheint, dass PDO_MYSQL und PDO_MYSQLND Unterstützung für mehrere Abfragen bieten, aber ich kann keine weiteren Informationen darüber finden. Wurden diese Projekte eingestellt? Gibt es jetzt eine Möglichkeit, mehrere Abfragen mit PDO auszuführen?

Gajus
quelle
4
Verwenden Sie SQL-Transaktionen.
Tereško
Warum möchten Sie mehrere Abfragen verwenden? Sie werden nicht abgewickelt, es ist genauso, als würden Sie sie nacheinander ausführen. IMHO keine Vor- und Nachteile. Im Fall von SQLInjection erlauben Sie dem Angreifer, zu tun, was er will.
Mleko
Es ist jetzt 2020 und PDO unterstützt dies - siehe meine Antwort weiter unten.
Andris

Antworten:

141

Wie ich weiß, PDO_MYSQLNDersetzt PDO_MYSQLin PHP 5.3. Verwirrender Teil ist, dass der Name immer noch ist PDO_MYSQL. Jetzt ist ND der Standardtreiber für MySQL + PDO.

Um mehrere Abfragen gleichzeitig ausführen zu können, benötigen Sie Folgendes:

  • PHP 5.3+
  • mysqlnd
  • Emulierte vorbereitete Aussagen. Stellen Sie sicher PDO::ATTR_EMULATE_PREPARES, dass 1(Standard) eingestellt ist. Alternativ können Sie die Verwendung vorbereiteter Anweisungen vermeiden und $pdo->execdirekt verwenden.

Mit 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);

Anweisungen verwenden

$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();

Eine Notiz:

Stellen Sie bei der Verwendung emulierter vorbereiteter Anweisungen sicher, dass Sie im DSN (verfügbar seit 5.3.6) die richtige Codierung festgelegt haben (die die tatsächliche Datencodierung widerspiegelt). Andernfalls besteht möglicherweise eine geringfügige Möglichkeit für die SQL-Injection, wenn eine ungerade Codierung verwendet wird .

Sam Dark
quelle
37
An der Antwort selbst ist nichts auszusetzen. Es wird erklärt, wie mehrere Abfragen ausgeführt werden. Ihre Annahme, dass die Antwort fehlerhaft ist, beruht auf der Annahme, dass die Abfrage Benutzereingaben enthält. Es gibt gültige Anwendungsfälle, in denen das gleichzeitige Senden mehrerer Abfragen die Leistung verbessern kann. Sie könnten vorschlagen, Verfahren als alternative Antwort auf diese Frage zu verwenden, aber das macht diese Antwort nicht schlecht.
Gajus
9
Der Code in dieser Antwort ist schlecht und fördert einige sehr schädliche Praktiken (Verwendung der Emulation zur Vorbereitung von Anweisungen, die den Code für die SQL-Injection-Schwachstelle offen machen ). Benutze es nicht.
Tereško
17
An dieser Antwort und insbesondere am Emulationsmodus ist nichts auszusetzen. Es ist standardmäßig in pdo_mysql aktiviert, und wenn es ein Problem gäbe, gäbe es bereits Tausende von Injektionen. Aber noch war niemand in der Nähe von einem. So geht es.
Ihr gesunder Menschenverstand
3
Tatsächlich war nur einer, der es schaffte, nicht nur Emotionen, sondern auch Argumente zu liefern, ircmaxell. Von ihm mitgebrachte Links sind jedoch ziemlich irrelevant. Erstens ist eine überhaupt nicht anwendbar, da ausdrücklich gesagt wird, dass "PDO immer gegen diesen Fehler immun ist". Während die zweite einfach durch Einstellen der richtigen Codierung lösbar ist. Es verdient also eine Notiz, keine Warnung und eine weniger ansprechende.
Ihr gesunder Menschenverstand
6
Als jemand zu sprechen, der ein Migrationstool schreibt, das SQL verwendet, das nur unsere Entwickler geschrieben haben (dh SQL-Injection ist kein Problem), hat mir massiv geholfen, und alle Kommentare, die darauf hinweisen, dass dieser Code schädlich ist, verstehen nicht alle Kontexte für seine Verwendung.
Luke
17

Nachdem ich einen halben Tag damit herumgespielt hatte, stellte ich fest, dass PDO einen Fehler hatte, bei dem ...

- -

//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;");

Es würde das ausführen "valid-stmt1;", anhalten "non-sense;"und niemals einen Fehler auslösen. Wird das nicht laufen lassen "valid-stmt3;", wahr zurückgeben und lügen, dass alles gut gelaufen ist.

Ich würde erwarten, dass es auf dem fehlerhaft ist, "non-sense;"aber es tut es nicht.

Hier habe ich diese Informationen gefunden: Eine ungültige PDO-Abfrage gibt keinen Fehler zurück

Hier ist der Fehler: https://bugs.php.net/bug.php?id=61613


Also habe ich versucht, dies mit mysqli zu tun und habe keine wirklich solide Antwort darauf gefunden, wie es funktioniert. Deshalb dachte ich, ich lasse es einfach hier für diejenigen, die es verwenden möchten.

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>";
}
Sai Phaninder Reddy J.
quelle
Funktioniert es, wenn Sie nur $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");ohne die beiden vorherigen Execs ausführen ? Ich kann es dazu bringen , Fehler in die Mitte zu werfen, aber nicht, wenn es nach erfolgreicher Ausführung ausgeführt wird .
Jeff Puckett
Nein, das tut es nicht. Das ist der Fehler bei PDO.
Sai Phaninder Reddy J
1
Meine schlechte, diese 3 $pdo->exec("")sind unabhängig voneinander. Ich habe sie jetzt aufgeteilt, um anzuzeigen, dass sie nicht in einer Reihenfolge vorliegen müssen, damit das Problem auftritt. Diese 3 sind 3 Konfigurationen zum Ausführen mehrerer Abfragen in einer exec-Anweisung.
Sai Phaninder Reddy J
Interessant. Hast du die Gelegenheit bekommen, meine gepostete Frage zu sehen? Ich frage mich, ob dies teilweise gepatcht wurde, weil ich den Fehler auslösen kann, wenn er der einzige execauf der Seite ist. Wenn ich jedoch mehrere mit execjeweils mehreren SQL-Anweisungen ausführe , reproduziere ich hier denselben Fehler. Aber wenn es das einzige execauf der Seite ist, kann ich es nicht reproduzieren.
Jeff Puckett
Hatte dieser execauf Ihrer Seite mehrere Aussagen?
Sai Phaninder Reddy J
3

Ein schneller und schmutziger Ansatz:

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);
    }
}

Teilt an angemessenen Endpunkten der SQL-Anweisung. Es gibt keine Fehlerprüfung, keinen Einspritzschutz. Verstehen Sie Ihre Verwendung, bevor Sie es verwenden. Persönlich verwende ich es zum Seeding von Rohmigrationsdateien für Integrationstests.

Bischof
quelle
1
Dies schlägt fehl, wenn Ihre SQL-Datei integrierte MySQL-Befehle enthält ... Es wird wahrscheinlich auch Ihr PHP-Speicherlimit sprengen, wenn die SQL-Datei groß ist ... Aufteilen in ;Pausen, wenn Ihr SQL Prozedur- oder Triggerdefinitionen enthält ... Viele Gründe, warum es nicht gut ist.
Bill Karwin
1

Wie Tausende von Menschen suche ich nach dieser Frage:
Kann mehrere Abfragen gleichzeitig ausführen, und wenn es einen Fehler gäbe, würde keiner ausgeführt. Ich bin überall auf diese Seite gegangen.
Aber obwohl die Freunde hier gute Antworten gaben, waren diese Antworten nicht gut für Mein Problem
Also habe ich eine Funktion geschrieben, die gut funktioniert und fast kein Problem mit SQL Injection hat.
Es könnte für diejenigen hilfreich sein, die nach ähnlichen Fragen suchen, deshalb habe ich sie hier verwendet

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;
}

zur Verwendung (Beispiel):

 $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);

und meine Verbindung:

    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();
    }

Hinweis: Mit
dieser Lösung können Sie mehrere Anweisungen zusammen
ausführen. Wenn eine falsche Anweisung auftritt, wird keine andere Anweisung ausgeführt

mirzaei.sajad
quelle
0

Versuchte folgenden Code

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

Dann

 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>';
 }

Und bekam

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

Wenn $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);nach hinzugefügt$db = ...

Dann bekam leere Seite

Wenn stattdessen SELECTversucht DELETE, dann bekam in beiden Fällen Fehler wie

 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

Also meine Schlussfolgerung, dass keine Injektion möglich ist ...

Andris
quelle
3
Sie sollten es eine neue Frage gemacht haben, wo auf diese Bezug genommen wird
Ihr gesunder Menschenverstand
Nicht so viele Fragen als Ergebnis dessen, was ich versucht habe. Und mein Fazit. Die anfängliche Frage ist alt, möglicherweise momentan nicht aktuell.
Andris
Ich bin mir nicht sicher, wie dies für irgendetwas in der Frage relevant ist.
CHao
in Frage sind Worte but you risk to be injected with multiple queries.Meine Antwort ist über Injektion
Andris
0

Probieren Sie diese Funktion aus: mehrere Abfragen und das Einfügen mehrerer Werte.

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();
}
Hassan b.
quelle
0

Die gU unterstützt dies (ab 2020). Führen Sie einfach wie gewohnt einen query () -Aufruf für ein PDO-Objekt aus und trennen Sie die Abfragen durch; und dann nextRowset (), um zum nächsten SELECT-Ergebnis zu gelangen, wenn Sie mehrere haben. Die Ergebnismengen werden in derselben Reihenfolge wie die Abfragen angezeigt. Denken Sie natürlich über die Auswirkungen auf die Sicherheit nach - akzeptieren Sie also keine vom Benutzer bereitgestellten Abfragen, verwenden Sie Parameter usw. Ich verwende sie beispielsweise bei Abfragen, die durch Code generiert werden.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());
Andris
quelle
Ich würde diese Art von Argumentation nie verstehen: "Hier ist ein Code, der ein großes Loch in der Sicherheit darstellt und alle empfohlenen bewährten Methoden vernachlässigt, sodass Sie über die Auswirkungen auf die Sicherheit nachdenken müssen." Wer sollte darüber nachdenken? Wann sollten sie nachdenken - bevor sie diesen Code verwenden oder nachdem sie gehackt werden? Warum denkst du nicht zuerst darüber nach, bevor du diese Funktion schreibst oder anderen Leuten anbietest?
Ihr gesunder Menschenverstand
Sehr geehrte @YourCommonSense, die mehrere Abfragen gleichzeitig ausführen, trägt zur Leistung bei. Weniger Netzwerkverkehr + Server können verwandte Abfragen optimieren. Mein (vereinfachtes) Beispiel sollte nur die Methode einführen, die für die Verwendung erforderlich ist. Es ist nur dann ein Sicherheitsloch, wenn Sie die bewährten Methoden, auf die Sie sich beziehen, nicht anwenden. Übrigens bin ich misstrauisch gegenüber Leuten, die sagen "Ich würde es nie verstehen ...", wenn sie es leicht könnten ... :-)
Andris