Prise en charge PDO pour plusieurs requêtes (PDO_MYSQL, PDO_MYSQLND)


102

Je sais que PDO ne prend pas en charge plusieurs requêtes exécutées dans une seule instruction. J'ai cherché sur Google et j'ai trouvé quelques articles parlant de PDO_MYSQL et PDO_MYSQLND.

PDO_MySQL est une application plus dangereuse que toute autre application MySQL traditionnelle. MySQL traditionnel n'autorise qu'une seule requête SQL. Dans PDO_MySQL, il n'y a pas de telle limitation, mais vous risquez d'être injecté avec plusieurs requêtes.

De: Protection contre l'injection SQL à l'aide de PDO et Zend Framework (juin 2010; par Julian)

Il semble que PDO_MYSQL et PDO_MYSQLND prennent en charge plusieurs requêtes, mais je ne peux pas trouver plus d'informations à leur sujet. Ces projets ont-ils été abandonnés? Existe-t-il maintenant un moyen d'exécuter plusieurs requêtes à l'aide de PDO.


4
Utilisez des transactions SQL.
tereško

Pourquoi souhaitez-vous utiliser plusieurs requêtes? Ils ne sont pas traités, c'est exactement la même chose que vous les exécuteriez les uns après les autres. IMHO pas d'avantages, seulement des inconvénients. Dans le cas de SQLInjection, vous autorisez l'attaquant à faire ce qu'il veut.
mleko

Nous sommes en 2020 maintenant, et PDO le soutient - voyez ma réponse ci-dessous.
Andris

Réponses:


141

Comme je le sais, PDO_MYSQLNDremplacé PDO_MYSQLdans PHP 5.3. Une partie déroutante est que le nom est toujours PDO_MYSQL. Alors maintenant, ND est le pilote par défaut pour MySQL + PDO.

Dans l'ensemble, pour exécuter plusieurs requêtes à la fois, vous avez besoin:

  • PHP 5.3+
  • mysqlnd
  • Déclarations préparées émulées. Assurez-vous que PDO::ATTR_EMULATE_PREPARESest défini sur 1(par défaut). Vous pouvez également éviter d'utiliser des instructions préparées et les utiliser $pdo->execdirectement.

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

Utiliser des déclarations

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

Une note:

Lorsque vous utilisez des instructions préparées émulées, assurez-vous que vous avez défini un codage approprié (qui reflète le codage réel des données) dans DSN (disponible depuis 5.3.6). Sinon, il peut y avoir une légère possibilité d'injection SQL si un encodage étrange est utilisé .


37
Il n'y a rien de mal avec la réponse elle-même. Il explique comment exécuter plusieurs requêtes. Votre hypothèse selon laquelle la réponse est erronée vient de l'hypothèse que la requête contient une entrée utilisateur. Il existe des cas d'utilisation valides dans lesquels l'envoi simultané de plusieurs requêtes peut améliorer les performances. Vous pourriez suggérer d'utiliser des procédures comme réponse alternative à cette question, mais cela ne rend pas cette réponse mauvaise.
Gajus

9
Le code de cette réponse est mauvais et favorise certaines pratiques très néfastes (utilisation de l'émulation pour les instructions Prepares, qui rendent le code ouvert à la vulnérabilité d'injection SQL ). Ne l'utilise pas.
tereško

17
Rien de mal avec cette réponse, et le mode d'émulation en particulier. Il est activé par défaut dans pdo_mysql, et s'il y avait un problème, il y aurait déjà des milliers d'injections. Mais personne n'en était encore proche. Alors ça va.
Votre bon sens

3
En fait, un seul qui a réussi à fournir non seulement des émotions mais des arguments, était ircmaxell. Cependant, les liens qu'il a apportés sont tout à fait hors de propos. Le premier est inapplicable du tout car il dit explicitement "PDO est toujours à l'abri de ce bogue." Alors que le second est simplement résoluble en définissant un codage approprié. Donc, il mérite une note, pas un avertissement, et moins attrayant.
Votre bon sens

6
S'exprimant en tant que personne qui écrit un outil de migration qui utilise SQL que seuls nos développeurs ont écrit (c'est-à-dire que l'injection SQL n'est pas un problème), cela m'a énormément aidé, et tout commentaire indiquant que ce code est nuisible ne comprend pas complètement tous les contextes pour son utilisation.
Luc le

17

Après une demi-journée à tripoter cela, j'ai découvert que PDO avait un bug où ...

-

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

Il exécuterait le "valid-stmt1;", s'arrêterait "non-sense;"et ne générerait jamais d'erreur. Ne fonctionnera pas "valid-stmt3;", retournera vrai et mentira que tout s'est bien passé.

Je m'attendrais à ce qu'il se trompe sur le "non-sense;"mais ce n'est pas le cas.

Voici où j'ai trouvé cette information: La requête PDO invalide ne renvoie pas d'erreur

Voici le bug: https://bugs.php.net/bug.php?id=61613


Donc, j'ai essayé de le faire avec mysqli et je n'ai pas vraiment trouvé de réponse solide sur son fonctionnement, alors j'ai pensé que je le laisserais ici pour ceux qui veulent l'utiliser.

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

Cela fonctionne-t-il si vous ne courez que $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");sans les deux précédents exécutifs? Je peux l'amener à lancer des erreurs au milieu, mais pas lorsqu'il est exécuté après des exécutifs réussis .
Jeff Puckett

Non, ce n'est pas le cas. C'est le bug avec PDO.
Sai Phaninder Reddy J

1
Mon mauvais, ces 3 $pdo->exec("")sont indépendants les uns des autres. Je les ai maintenant divisés pour indiquer qu'ils n'ont pas besoin d'être dans un ordre pour que le problème se pose. Ces 3 sont 3 configurations d'exécution de plusieurs requêtes dans une seule instruction exec.
Sai Phaninder Reddy J

Intéressant. Avez-vous eu la chance de voir ma question posée? Je me demande si cela a été partiellement corrigé car je peux obtenir l'erreur si c'est la seule execsur la page, mais si j'exécute plusieurs execchacun avec plusieurs instructions SQL, alors je reproduis le même bogue ici. Mais si c'est le seul execsur la page, je ne peux pas le reproduire.
Jeff Puckett

Celui execsur votre page contenait-il plusieurs déclarations?
Sai Phaninder Reddy J

3

Une approche rapide et sale:

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

Se divise à des points de fin d'instruction SQL raisonnables. Il n'y a pas de contrôle d'erreur, pas de protection contre l'injection. Comprenez votre utilisation avant de l'utiliser. Personnellement, je l'utilise pour semer des fichiers de migration bruts pour les tests d'intégration.


1
Cela échoue si votre fichier SQL contient des commandes intégrées mysql ... Cela fera probablement exploser votre limite de mémoire PHP aussi, si le fichier SQL est volumineux ... Fractionnement lors des ;pauses si votre SQL contient des définitions de procédure ou de déclencheur ... raisons pour lesquelles ce n'est pas bon.
Bill Karwin

1

Comme des milliers de personnes, je cherche cette question:
Peut exécuter plusieurs requêtes simultanément, et s'il y avait une erreur, aucune ne s'exécuterait Je suis allé sur cette page partout
Mais bien que les amis ici aient donné de bonnes réponses, ces réponses n'étaient pas bonnes pour mon problème
J'ai donc écrit une fonction qui fonctionne bien et qui n'a presque aucun problème avec SQL Injection.
Cela pourrait être utile pour ceux qui recherchent des questions similaires, je les mets ici pour les utiliser

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

à utiliser (exemple):

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

et ma connexion:

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

Remarque:
cette solution vous aide à exécuter plusieurs instructions ensemble.Si
une instruction incorrecte se produit, elle n'exécute aucune autre instruction


0

Code suivant essayé

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

ensuite

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

Et j'ai

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

Si ajouté $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);après$db = ...

Puis j'ai une page blanche

Si à la place SELECTessayé DELETE, alors dans les deux cas une erreur comme

 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

Donc ma conclusion qu'aucune injection n'est possible ...


3
Vous auriez dû en faire une nouvelle question où faire référence à celle-ci
Votre bon sens

Pas tellement de questions à la suite de ce que j'ai essayé. Et ma conclusion. La question initiale est ancienne, peut-être pas actuelle pour le moment.
Andris

Je ne sais pas comment cela est pertinent pour quoi que ce soit dans la question.
cHao

en question sont des mots but you risk to be injected with multiple queries.Ma réponse concerne l'injection
Andris

0

Essayez cette fonction: plusieurs requêtes et insertion de valeurs multiples.

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 le soutient (à partir de 2020). Faites simplement un appel query () sur un objet PDO comme d'habitude, en séparant les requêtes par; puis nextRowset () pour passer au résultat SELECT suivant, si vous en avez plusieurs. Les ensembles de résultats seront dans le même ordre que les requêtes. Pensez évidemment aux implications de sécurité - alors n'acceptez pas les requêtes fournies par l'utilisateur, utilisez les paramètres, etc. Je l'utilise avec des requêtes générées par code par exemple.

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

Je ne comprendrais jamais ce genre de raisonnement, "Voici un code qui est un gros trou dans la sécurité en négligeant toutes les bonnes pratiques recommandées, vous devez donc réfléchir aux implications de sécurité." Qui devrait y penser? Quand devraient-ils réfléchir - avant d'utiliser ce code ou après avoir été piraté? Pourquoi n'y pensez-vous pas d'abord, avant d'écrire cette fonction ou de la proposer à d'autres personnes?
Votre bon sens le

Cher @YourCommonSense, exécuter plusieurs requêtes en une seule fois améliore les performances, moins de trafic réseau + serveur peut optimiser les requêtes associées. Mon exemple (simplifié) ne visait qu'à introduire la méthode requise pour l'utiliser. C'est un trou de sécurité uniquement si vous n'utilisez pas les bonnes pratiques auxquelles vous faites référence. BTW, je me méfie des gens qui disent « Je ne comprendrais jamais ... » quand ils pourraient facilement ... :-)
Andris
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.