Pourquoi ne devrais-je pas utiliser les fonctions mysql_ * en PHP?

2502

Quelles sont les raisons techniques pour lesquelles il ne faut pas utiliser de mysql_*fonctions? (par exemple mysql_query(), mysql_connect()ou mysql_real_escape_string())?

Pourquoi devrais-je utiliser autre chose même s'ils fonctionnent sur mon site?

S'ils ne fonctionnent pas sur mon site, pourquoi ai-je des erreurs comme

Avertissement: mysql_connect (): aucun fichier ou répertoire de ce type

Le fantôme de Madara
la source
Erreur à ressembler: Erreur fatale: Erreur non interceptée: Appel à la fonction non définie mysql_connect () ...
Bimal Poudel
21
La dépréciation seule est une raison suffisante pour les éviter
Sasa1234

Réponses:

2089

L'extension MySQL:

  • N'est pas en développement actif
  • Est officiellement obsolète à partir de PHP 5.5 (publié en juin 2013).
  • A été entièrement supprimé à partir de PHP 7.0 (publié en décembre 2015)
    • Cela signifie qu'au 31 décembre 2018, il n'existe dans aucune version prise en charge de PHP. Si vous utilisez une version de PHP qui le prend en charge, vous utilisez une version qui ne résout pas les problèmes de sécurité.
  • Manque une interface OO
  • Ne prend pas en charge:
    • Requêtes asynchrones non bloquantes
    • Instructions préparées ou requêtes paramétrées
    • Procédures stockées
    • Déclarations multiples
    • Transactions
    • La "nouvelle" méthode d'authentification par mot de passe (activée par défaut dans MySQL 5.6; requise dans 5.7)
    • L'une des nouvelles fonctionnalités de MySQL 5.1 ou version ultérieure

Comme il est obsolète, son utilisation rend votre code moins pérenne.

Le manque de prise en charge des instructions préparées est particulièrement important car elles fournissent une méthode plus claire et moins sujette aux erreurs pour échapper et citer des données externes que de les échapper manuellement avec un appel de fonction distinct.

Voir la comparaison des extensions SQL .

Quentin
la source
287
La dépréciation seule est une raison suffisante pour les éviter. Ils ne seront pas là un jour et vous ne serez pas heureux si vous comptez sur eux. Le reste n'est qu'une liste de choses que l'utilisation des anciennes extensions a empêché les gens d'apprendre.
Tim Post
111
La dépréciation n'est pas la solution miracle que tout le monde semble penser. PHP lui-même ne sera pas là un jour, mais nous comptons sur les outils dont nous disposons aujourd'hui. Lorsque nous devons changer d'outils, nous le ferons.
Courses de légèreté en orbite
133
@LightnessRacesinOrbit - La dépréciation n'est pas une solution miracle, c'est un drapeau qui dit "Nous reconnaissons que cela craint donc nous n'allons pas le supporter plus longtemps". Bien qu'une meilleure vérification du code à l'avenir soit une bonne raison de s'éloigner des fonctionnalités obsolètes, ce n'est pas la seule (ni même la principale). Changer d'outils parce qu'il y a de meilleurs outils, pas parce que vous y êtes obligé. (Et changer d'outils avant d'être forcé signifie que vous n'apprenez pas les nouveaux simplement parce que votre code a cessé de fonctionner et doit être corrigé hier ... ce qui est le pire moment pour apprendre de nouveaux outils).
Quentin
18
Une chose que je n'ai pas vue mentionnée sur le manque de déclarations préparées est le problème de performance. Chaque fois que vous émettez une instruction, quelque chose doit la compiler pour que le démon MySQL puisse la comprendre. Avec cette API, si vous émettez 200 000 de la même requête dans une boucle, c'est 200 000 fois que la requête doit être compilée pour que MySQL la comprenne. Avec les instructions préparées, il est compilé une fois, puis les valeurs sont paramétrées dans le SQL compilé.
Goldentoa11
20
@symcbean, il ne prend certainement pas en charge les instructions préparées. C'est en fait la principale raison pour laquelle il est obsolète. Sans instructions préparées (faciles à utiliser), l'extension mysql est souvent victime d'attaques par injection SQL.
rustyx
1287

PHP propose trois API différentes pour se connecter à MySQL. Ce sont les mysql(supprimés à partir de PHP 7) mysqli, et les PDOextensions.

Les mysql_*fonctions étaient très populaires, mais leur utilisation n'est plus encouragée. L'équipe de documentation discute de la situation de la sécurité de la base de données et éduquer les utilisateurs à s'éloigner de l'extension ext / mysql couramment utilisée en fait partie (consultez php.internals: déprécier ext / mysql ).

Et l'équipe de développement de PHP plus tard a pris la décision de générer des E_DEPRECATEDerreurs lorsque les utilisateurs se connectent à MySQL, que ce soit par mysql_connect(), mysql_pconnect()ou la fonctionnalité de connexion implicite intégrée dans ext/mysql.

ext/mysqla été officiellement déconseillé depuis PHP 5.5 et a été supprimé depuis PHP 7 .

Voir la boîte rouge?

Lorsque vous allez sur n'importe quelle mysql_*page de manuel de fonction, vous voyez une boîte rouge, expliquant qu'elle ne devrait plus être utilisée.

Pourquoi


S'éloigner ext/mysqln'est pas seulement une question de sécurité, mais aussi d'avoir accès à toutes les fonctionnalités de la base de données MySQL.

ext/mysqla été construit pour MySQL 3.23 et n'a reçu que très peu d'ajouts depuis, tout en conservant la compatibilité avec cette ancienne version, ce qui rend le code un peu plus difficile à maintenir. Les fonctionnalités manquantes qui ne sont pas prises en charge ext/mysqlincluent: (à partir du manuel PHP ).

Raison de ne pas utiliser la mysql_*fonction :

  • Pas en développement actif
  • Supprimé à partir de PHP 7
  • Manque une interface OO
  • Ne prend pas en charge les requêtes asynchrones non bloquantes
  • Ne prend pas en charge les instructions préparées ou les requêtes paramétrées
  • Ne prend pas en charge les procédures stockées
  • Ne prend pas en charge plusieurs instructions
  • Ne prend pas en charge les transactions
  • Ne prend pas en charge toutes les fonctionnalités de MySQL 5.1

Ci-dessus point cité de la réponse de Quentin

Le manque de prise en charge des instructions préparées est particulièrement important car elles fournissent une méthode plus claire et moins sujette aux erreurs pour échapper et citer des données externes que de les échapper manuellement avec un appel de fonction distinct.

Voir la comparaison des extensions SQL .


Suppression des avertissements de dépréciation

Pendant la conversion du code en MySQLi/ PDO, les E_DEPRECATEDerreurs peuvent être supprimées en définissant error_reportingdans php.ini pour exclureE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Notez que cela masquera également d' autres avertissements de dépréciation , qui peuvent cependant concerner d'autres choses que MySQL. ( du manuel PHP )

L'article PDO vs MySQLi: lequel utiliser? par Dejan Marjanovic vous aidera à choisir.

Et une meilleure façon est PDO, et j'écris maintenant un PDOtutoriel simple .


Un tutoriel PDO simple et court


Q. Ma première question était: qu'est-ce que «AOP»?

A. « PDO - PHP Data Objects - est une couche d'accès à la base de données fournissant une méthode uniforme d'accès à plusieurs bases de données.»

texte alternatif


Connexion à MySQL

Avec mysql_*fonction ou on peut le dire à l'ancienne (obsolète en PHP 5.5 et supérieur)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

Avec PDO: il vous suffit de créer un nouvel PDOobjet. Le constructeur accepte les paramètres pour spécifier la source de base de données PDOconstructeur de la plupart prend quatre paramètres qui sont DSN(nom de la source de données) et le cas échéant username, password.

Ici, je pense que vous connaissez tout sauf DSN; c'est nouveau dans PDO. A DSNest essentiellement une chaîne d'options indiquant le PDOpilote à utiliser et les détails de connexion. Pour plus d'informations, consultez PDO MySQL DSN .

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Remarque: vous pouvez également utiliser charset=UTF-8, mais parfois cela provoque une erreur, il est donc préférable de l'utiliser utf8.

S'il y a une erreur de connexion, il lancera un PDOExceptionobjet qui peut être capturé pour être manipulé Exceptiondavantage.

Bonne lecture : Connexions et gestion des connexions ¶

Vous pouvez également passer plusieurs options de pilote sous forme de tableau au quatrième paramètre. Je recommande de passer le paramètre qui met PDOen mode exception. Étant donné que certains PDOpilotes ne prennent pas en charge les instructions préparées natives, PDOexécute donc l' émulation de la préparation. Il vous permet également d'activer manuellement cette émulation. Pour utiliser les instructions natives préparées côté serveur, vous devez les définir explicitement false.

L'autre consiste à désactiver la préparation de l'émulation qui est activée dans le MySQLpilote par défaut, mais la préparation de l'émulation doit être désactivée pour une utilisation en PDOtoute sécurité.

J'expliquerai plus tard pourquoi la préparation de l'émulation doit être désactivée. Pour trouver la raison, veuillez consulter cet article .

Il n'est utilisable que si vous utilisez une ancienne version MySQLdont je ne recommande pas.

Voici un exemple de la façon dont vous pouvez le faire:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Pouvons-nous définir des attributs après la construction de PDO?

Oui , nous pouvons également définir certains attributs après la construction de PDO avec la setAttributeméthode:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

La gestion des erreurs


La gestion des erreurs est beaucoup plus simple PDOque mysql_*.

Une pratique courante lors de l'utilisation mysql_*est:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()n'est pas un bon moyen de gérer l'erreur car nous ne pouvons pas gérer la chose die. Il terminera simplement le script brusquement, puis fera écho de l'erreur à l'écran que vous ne voulez généralement pas montrer à vos utilisateurs finaux, et permettra aux pirates sanglants de découvrir votre schéma. Alternativement, les valeurs de retour des mysql_*fonctions peuvent souvent être utilisées en conjonction avec mysql_error () pour gérer les erreurs.

PDOoffre une meilleure solution: les exceptions. Tout ce que nous faisons avec PDOdevrait être enveloppé dans un try- catchbloc. Nous pouvons forcer PDOdans l'un des trois modes d'erreur en définissant l'attribut de mode d'erreur. Trois modes de gestion des erreurs sont présentés ci-dessous.

  • PDO::ERRMODE_SILENT. Il s'agit simplement de définir des codes d'erreur et agit à peu près de la même manière que mysql_*lorsque vous devez vérifier chaque résultat, puis regarder $db->errorInfo();pour obtenir les détails de l'erreur.
  • PDO::ERRMODE_WARNINGRelevez E_WARNING. (Avertissements d'exécution (erreurs non fatales). L'exécution du script n'est pas interrompue.)
  • PDO::ERRMODE_EXCEPTION: Lève des exceptions. Il représente une erreur déclenchée par PDO. Vous ne devez pas jeter un PDOExceptionde votre propre code. Voir Exceptions pour plus d'informations sur les exceptions en PHP. Il agit très bien comme or die(mysql_error());, quand il n'est pas attrapé. Mais contrairement à or die(), le PDOExceptionpeut être attrapé et manipulé avec élégance si vous choisissez de le faire.

Bonne lecture :

Comme:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Et vous pouvez l'envelopper try- catch, comme ci-dessous:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Vous n'avez pas à gérer avec try- en catchce moment. Vous pouvez l'attraper à tout moment approprié, mais je vous recommande fortement d'utiliser try- catch. Il peut également être plus judicieux de l'attraper en dehors de la fonction qui appelle le PDOtruc:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

De plus, vous pouvez gérer par or die()ou nous pouvons dire comme mysql_*, mais ce sera vraiment varié. Vous pouvez masquer les messages d'erreur dangereux en production en tournant display_errors offet en lisant simplement votre journal des erreurs.

Maintenant, après avoir lu toutes les choses ci - dessus, vous pensez probablement: ce que le diable est que quand je veux juste commencer à se penchant simples SELECT, INSERT, UPDATE, ou DELETEdéclarations? Ne vous inquiétez pas, c'est parti:


Sélection des données

AOP sélectionner l'image

Donc, ce que vous faites, mysql_*c'est:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Maintenant PDO, vous pouvez faire ceci comme:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Ou

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Remarque : Si vous utilisez la méthode comme ci-dessous ( query()), cette méthode renvoie un PDOStatementobjet. Donc, si vous voulez récupérer le résultat, utilisez-le comme ci-dessus.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

Dans PDO Data, il est obtenu via la ->fetch(), une méthode de votre descripteur d'instructions. Avant d'appeler la récupération, la meilleure approche serait de dire à PDO comment vous souhaitez que les données soient récupérées. Dans la section ci-dessous, j'explique cela.

Modes de récupération

Notez l'utilisation de PDO::FETCH_ASSOCdans le code fetch()et fetchAll()ci-dessus. Cela indique PDOde renvoyer les lignes sous forme de tableau associatif avec les noms de champ comme clés. Il existe également de nombreux autres modes de récupération que j'expliquerai un par un.

Tout d'abord, j'explique comment sélectionner le mode de récupération:

 $stmt->fetch(PDO::FETCH_ASSOC)

Dans ce qui précède, j'ai utilisé fetch(). Vous pouvez aussi utiliser:

Maintenant j'arrive en mode fetch:

  • PDO::FETCH_ASSOC: retourne un tableau indexé par nom de colonne tel que renvoyé dans votre jeu de résultats
  • PDO::FETCH_BOTH (par défaut): renvoie un tableau indexé à la fois par le nom de la colonne et le numéro de colonne indexé par 0, tel que renvoyé dans votre jeu de résultats

Il y a encore plus de choix! Lisez-les tous dans la PDOStatementdocumentation Fetch. .

Obtenir le nombre de lignes :

Au lieu d'utiliser mysql_num_rowspour obtenir le nombre de lignes retournées, vous pouvez obtenir un PDOStatementet faire rowCount(), comme:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Obtention du dernier ID inséré

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Insérer et mettre à jour ou supprimer des instructions

Insérer et mettre à jour l'image PDO

Ce que nous faisons en mysql_*fonction, c'est:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

Et dans pdo, cette même chose peut être faite par:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

Dans la requête ci-dessus, PDO::execexécutez une instruction SQL et retourne le nombre de lignes affectées.

L'insertion et la suppression seront traitées ultérieurement.

La méthode ci-dessus n'est utile que lorsque vous n'utilisez pas de variable dans la requête. Mais lorsque vous devez utiliser une variable dans une requête, n'essayez jamais comme ci-dessus et là pour l' instruction préparée ou l'instruction paramétrée .


Déclarations préparées

Q. Qu'est-ce qu'une déclaration préparée et pourquoi en ai-je besoin?
A. Une instruction préparée est une instruction SQL précompilée qui peut être exécutée plusieurs fois en envoyant uniquement les données au serveur.

Le flux de travail typique de l'utilisation d'une instruction préparée est le suivant ( cité dans Wikipedia trois points 3 ):

  1. Préparer : le modèle d'instruction est créé par l'application et envoyé au système de gestion de base de données (SGBD). Certaines valeurs ne sont pas spécifiées, appelées paramètres, espaces réservés ou variables de liaison (étiquetées ?ci-dessous):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. Le SGBD analyse, compile et effectue l'optimisation des requêtes sur le modèle d'instruction et stocke le résultat sans l'exécuter.

  3. Exécuter : ultérieurement, l'application fournit (ou lie) des valeurs pour les paramètres, et le SGBD exécute l'instruction (renvoyant éventuellement un résultat). L'application peut exécuter l'instruction autant de fois qu'elle le souhaite avec des valeurs différentes. Dans cet exemple, il peut fournir «Pain» pour le premier paramètre et 1.00pour le deuxième paramètre.

Vous pouvez utiliser une instruction préparée en incluant des espaces réservés dans votre SQL. Il y en a essentiellement trois sans espaces réservés (n'essayez pas avec la variable ci-dessus), un avec des espaces réservés sans nom et un avec des espaces réservés nommés.

Q. Alors maintenant, quels sont les espaces réservés nommés et comment les utiliser?
A. Espaces réservés nommés. Utilisez des noms descriptifs précédés de deux points, au lieu de points d'interrogation. Nous ne nous soucions pas de la position / de l'ordre de valeur dans le nom de la marque de réservation:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Vous pouvez également lier à l'aide d'un tableau d'exécution:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Une autre fonctionnalité intéressante pour les OOPamis est que les espaces réservés nommés ont la possibilité d'insérer des objets directement dans votre base de données, en supposant que les propriétés correspondent aux champs nommés. Par exemple:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q. Alors maintenant, quels sont les espaces réservés sans nom et comment les utiliser?
A. Prenons un exemple:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

et

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

Dans ce qui précède, vous pouvez les voir ?au lieu d'un nom comme dans un espace réservé au nom. Maintenant, dans le premier exemple, nous affectons des variables aux différents espaces réservés ( $stmt->bindValue(1, $name, PDO::PARAM_STR);). Ensuite, nous attribuons des valeurs à ces espaces réservés et exécutons l'instruction. Dans le deuxième exemple, le premier élément du tableau va au premier ?et le second au second ?.

REMARQUE : dans les espaces réservés sans nom, nous devons prendre soin du bon ordre des éléments du tableau que nous transmettons à la PDOStatement::execute()méthode.


SELECT, INSERT, UPDATE, DELETEPréparé les requêtes

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();

REMARQUE:

Cependant PDOet / ou MySQLine sont pas complètement sûrs. Vérifiez la réponse Les instructions préparées par PDO sont-elles suffisantes pour empêcher l'injection SQL? par ircmaxell . Aussi, je cite une partie de sa réponse:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
NullPoiиteя
la source
15
Ce que la bonne lecture ci-dessus devrait mentionner: une déclaration préparée enlève toute utilisation significative du IN (...) construct.
Eugen Rieck
24
La question était "Pourquoi ne devrais-je pas utiliser les fonctions mysql_ * en PHP". Cette réponse, bien qu'impressionnante et pleine d'informations utiles, sort du cadre et comme le dit @trejder - 8 personnes sur 10 vont manquer ces informations simplement parce qu'elles n'ont pas 4 heures à passer à travailler il. Ce serait beaucoup plus précieux divisé et utilisé comme réponses à plusieurs questions plus précises.
Alex McMillan
Personnellement, je préfère mysqli et PDO. Mais pour la gestion des dés, j'ai essayé une alternative function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();d'exception. Cela fonctionne pour lancer des exceptions.
kuldeep.kamboj
vous Doesn't support non-blocking, asynchronous queriesindiquez comme raison de ne pas utiliser mysql_ - vous devez également l'indiquer comme raison de ne pas utiliser PDO, car PDO ne prend pas cela en charge non plus. (mais MySQLi le supporte)
hanshenrik
est-il possible d'utiliser Charset utf8mb4_unicode_ci car j'ai une base de données qui l'utilise?
Ryan Stone
301

Tout d'abord, commençons par le commentaire standard que nous donnons à tout le monde:

Veuillez ne pas utiliser de mysql_*fonctions dans le nouveau code . Ils ne sont plus entretenus et sont officiellement obsolètes . Vous voyez la boîte rouge ? Découvrezplutôt les instructions préparées et utilisez PDO ou MySQLi - cet article vous aidera à décider lesquelles. Si vous choisissez PDO, voici un bon tutoriel .

Passons en revue, phrase par phrase, et expliquons:

  • Ils ne sont plus entretenus et sont officiellement obsolètes

    Cela signifie que la communauté PHP abandonne progressivement le support de ces très anciennes fonctions. Ils n'existeront probablement pas dans une future version (récente) de PHP! L'utilisation continue de ces fonctions peut casser votre code dans un avenir (pas si lointain).

    NOUVEAU! - ext / mysql est désormais officiellement obsolète depuis PHP 5.5!

    Plus récent! ext / mysql a été supprimé en PHP 7 .

  • Au lieu de cela, vous devriez apprendre des déclarations préparées

    mysql_*L'extension ne prend pas en charge les instructions préparées , ce qui est (entre autres) une contre-mesure très efficace contre l' injection SQL . Il a corrigé une vulnérabilité très grave dans les applications dépendantes de MySQL qui permet aux attaquants d'accéder à votre script et d'effectuer toute requête possible sur votre base de données.

    Pour plus d'informations, consultez Comment puis-je empêcher l'injection SQL en PHP?

  • Voir la boîte rouge?

    Lorsque vous accédez à une mysqlpage de manuel de fonction, vous voyez un cadre rouge, expliquant qu'il ne doit plus être utilisé.

  • Utilisez PDO ou MySQLi

    Il existe des alternatives meilleures, plus robustes et bien construites, PDO - PHP Database Object , qui offre une approche OOP complète de l'interaction avec la base de données, et MySQLi , qui est une amélioration spécifique à MySQL.

Le fantôme de Madara
la source
6
Il y a encore une chose: je pense que cette fonction existe toujours en PHP pour une seule raison - la compatibilité avec les anciens CMS obsolètes mais toujours en cours d'exécution, le commerce électronique, les systèmes de tableaux d'affichage, etc. Enfin, elle sera supprimée et vous devrez réécrire votre application ...
Kamil
4
@Kamil: C'est vrai, mais ce n'est pas vraiment une raison pour laquelle vous ne devriez pas l'utiliser. La raison de ne pas l'utiliser est parce qu'elle est ancienne, précaire, etc. :)
Madara's Ghost
4
@Mario - les développeurs PHP ont un processus, et ils viennent de voter en faveur de la dépréciation formelle de ext / mysql à partir de 5.5. Ce n'est plus un problème hypothétique.
SDC du
2
L'ajout de quelques lignes supplémentaires avec une technique éprouvée telle que PDO ou MySQLi offre toujours la facilité d'utilisation que PHP a toujours offerte. J'espère que pour le développeur, il / elle sait que voir ces terribles fonctions mysql_ * dans n'importe quel tutoriel nuit en fait à la leçon, et devrait dire à l'OP que ce type de code est tellement il y a 10 ans - et devrait remettre en question le la pertinence du tutoriel aussi!
FredTheWebGuy
1
Ce que la réponse devrait mentionner de façon appropriée: une déclaration préparée enlève toute utilisation significative du IN (...) construct.
Eugen Rieck
217

Facilité d'utilisation

Les raisons analytiques et synthétiques ont déjà été évoquées. Pour les nouveaux arrivants, il y a une incitation plus importante à cesser d'utiliser les fonctions datées de mysql_.

Les API de base de données contemporaines sont simplement plus faciles à utiliser.

Ce sont surtout les paramètres liés qui peuvent simplifier le code. Et avec d' excellents tutoriels (comme vu ci-dessus), la transition vers PDO n'est pas trop difficile.

Cependant, la réécriture d'une base de code plus importante prend du temps. Raison d'être de cette alternative intermédiaire:

Fonctions pdo_ * équivalentes à la place de mysql_ *

En utilisant < pdo_mysql.php >, vous pouvez passer des anciennes fonctions mysql_ avec un minimum d'effort . Il ajoute pdo_des wrappers de fonction qui remplacent leurs mysql_homologues.

  1. Simplement dans chaque script d'invocation qui doit interagir avec la base de données. include_once("pdo_mysql.php");

  2. Supprimez le mysql_préfixe de la fonction partout et remplacez-le par pdo_.

    • mysql_connect() devient pdo_connect()
    • mysql_query() devient pdo_query()
    • mysql_num_rows() devient pdo_num_rows()
    • mysql_insert_id() devient pdo_insert_id()
    • mysql_fetch_array() devient pdo_fetch_array()
    • mysql_fetch_assoc() devient pdo_fetch_assoc()
    • mysql_real_escape_string() devient pdo_real_escape_string()
    • etc...

  3. Votre code fonctionnera de la même manière et restera généralement le même:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }

Et voilà.
Votre code utilise PDO.
Il est maintenant temps de l' utiliser réellement .

Les paramètres liés peuvent être faciles à utiliser

Vous avez juste besoin d'une API moins lourde.

pdo_query()ajoute un support très facile pour les paramètres liés. La conversion de l'ancien code est simple:

Déplacez vos variables hors de la chaîne SQL.

  • Ajoutez-les en tant que paramètres de fonction délimités par des virgules dans pdo_query().
  • Placez les points d'interrogation en ?tant qu'espaces réservés là où les variables se trouvaient auparavant.
  • Débarrassez-vous des 'guillemets simples qui contenaient précédemment des valeurs / variables de chaîne.

L'avantage devient plus évident pour un code plus long.

Souvent, les variables de chaîne ne sont pas seulement interpolées en SQL, mais concaténées avec des appels d'échappement entre les deux.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

Avec les ?espaces réservés appliqués, vous n'avez pas à vous soucier de cela:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

N'oubliez pas que pdo_ * autorise toujours soit ou .
N'échappez simplement pas à une variable et liez-la dans la même requête.

  • La fonction d'espace réservé est fournie par le véritable PDO derrière lui.
  • Ainsi, les :namedlistes d'espace réservé ont également été autorisées ultérieurement.

Plus important encore, vous pouvez passer des variables $ _REQUEST [] en toute sécurité derrière n'importe quelle requête. Lorsque les <form>champs soumis correspondent exactement à la structure de la base de données, c'est encore plus court:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Tant de simplicité. Mais revenons à quelques conseils de réécriture et à des raisons techniques pour lesquelles vous voudrez peut-être vous débarrasser mysql_et vous échapper.

Corrigez ou supprimez toute sanitize()fonction oldschool

Une fois que vous avez converti tous les mysql_appels en pdo_queryparamètres paramétrés, supprimez tous les pdo_real_escape_stringappels redondants .

En particulier , vous devez fixer une sanitizeou cleanou filterThisou clean_datafonctions comme annoncé par des tutoriels datés sous une forme ou l'autre:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Le bogue le plus flagrant ici est le manque de documentation. Plus important encore, l'ordre de filtrage était exactement dans le mauvais ordre.

  • L'ordre correct aurait été: déprécié en stripslashestant qu'appel le plus interne, puis trim, par la suite strip_tags, htmlentitiespour le contexte de sortie, et enfin enfin, _escape_stringcar son application devrait précéder directement l'interpénétration SQL.

  • Mais comme première étape, débarrassez-vous de l'_real_escape_string appel.

  • Vous devrez peut-être conserver le reste de votre sanitize()fonction pour l'instant si votre base de données et votre flux d'application attendent des chaînes sécurisées pour le contexte HTML. Ajoutez un commentaire qu'il applique désormais uniquement le HTML s'échappant.

  • La gestion des chaînes / valeurs est déléguée à PDO et à ses instructions paramétrées.

  • S'il y avait une mention stripslashes()dans votre fonction de désinfection, cela peut indiquer une surveillance de niveau supérieur.

    Note historique sur magic_quotes. Cette fonctionnalité est à juste titre déconseillée. Il est souvent décrit à tort comme une sécurité défaillanteCependant, fonction de . Mais les magic_quotes sont autant une fonction de sécurité défaillante que les balles de tennis ont échoué comme source de nutrition. Ce n'était tout simplement pas leur objectif.

    L'implémentation originale en PHP2 / FI l'a introduite explicitement avec juste "les guillemets seront automatiquement échappés ce qui facilitera le passage des données de formulaire directement aux requêtes msql ". Notamment, il était accidentellement sûr de l'utiliser avec mSQL , car il ne supportait que l'ASCII.
    Puis PHP3 / Zend a réintroduit magic_quotes pour MySQL et l'a mal documenté. Mais à l'origine, c'était juste une fonctionnalité pratique , pas destinée à la sécurité.

Différences entre les déclarations préparées

Lorsque vous brouillez des variables de chaîne dans les requêtes SQL, il n'est pas seulement plus complexe à suivre. C'est également un effort inutile pour MySQL de séparer à nouveau le code et les données.

Les injections SQL se produisent simplement lorsque les données se transforment en code contexte du . Un serveur de base de données ne peut pas repérer plus tard où PHP a initialement collé des variables entre les clauses de requête.

Avec les paramètres liés, vous séparez le code SQL et les valeurs de contexte SQL dans votre code PHP. Mais il n'est pas remanié dans les coulisses (sauf avec PDO :: EMULATE_PREPARES). Votre base de données reçoit les commandes SQL non modifiées et les valeurs de variable 1: 1.

Bien que cette réponse souligne que vous devez vous soucier des avantages de lisibilité de la suppression mysql_. Il y a aussi parfois un avantage en termes de performances (INSERT répétés avec des valeurs juste différentes) en raison de cette séparation visible et technique des données / codes.

Attention, la liaison de paramètres n'est toujours pas une solution magique contre tous les injections SQL. Il gère l'utilisation la plus courante des données / valeurs. Mais ne peut pas mettre en liste blanche les identificateurs de nom / table de colonne, aider à la construction de clauses dynamiques ou simplement des listes de valeurs de tableau simples.

Utilisation de PDO hybride

Ces pdo_*fonctions d'encapsuleur font une API de stop-gap facile à coder. (C'est à peu près ce qui MYSQLIaurait pu être sans le changement de signature de fonction idiosyncrasique). Ils exposent également la vraie AOP dans la plupart des cas.
La réécriture ne doit pas s'arrêter à l'utilisation des nouveaux noms de fonctions pdo_. Vous pouvez effectuer une transition une par une chaque pdo_query () dans un appel $ pdo-> prepare () -> execute ().

Il est toutefois préférable de recommencer à simplifier. Par exemple, l'extraction de résultats courante:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Peut être remplacé par juste une itération foreach:

foreach ($result as $row) {

Ou mieux encore, une récupération directe et complète des baies:

$result->fetchAll();

Vous obtiendrez des avertissements plus utiles dans la plupart des cas que PDO ou mysql_ fournissent généralement après l'échec des requêtes.

Autres options

J'espère donc que cela a permis de visualiser certaines raisons pratiques et une voie intéressante à abandonner mysql_.

Passer simplement à ne le coupe pas tout à fait. pdo_query()est aussi juste un frontend dessus.

À moins que vous n'introduisiez également une liaison de paramètres ou que vous ne puissiez utiliser autre chose à partir de la meilleure API, il s'agit d'un commutateur inutile. J'espère qu'il est décrit assez simplement pour ne pas décourager davantage les nouveaux arrivants. (L'éducation fonctionne généralement mieux que la prohibition.)

Bien qu'il soit admissible à la catégorie des choses les plus simples qui pourraient éventuellement fonctionner, il s'agit également d'un code très expérimental. Je viens de l'écrire le week-end. Il existe cependant une pléthore d'alternatives. Il suffit de google pour l' abstraction de la base de données PHP et de parcourir un peu. Il y a toujours eu et il y aura beaucoup d'excellentes bibliothèques pour de telles tâches.

Si vous souhaitez simplifier davantage l'interaction de votre base de données, des cartographes comme Paris / Idiorm valent la peine d'être essayés . Tout comme personne n'utilise plus le DOM fade en JavaScript, vous n'avez plus besoin de garder une interface de base de données brute de nos jours.

mario
la source
8
Soyez prudent avec la pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);fonction - à savoir:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
rickyduck
@Tom Bien sûr, bien qu'il ne soit pas beaucoup maintenu (0.9.2 était le dernier), vous pouvez créer un compte fossile , ajouter au wiki ou déposer un rapport de bogue (sans inscription IIRC).
mario
pdo_real_escape_string() <- Est-ce même une fonction réelle, je ne trouve aucune documentation pour cela? Veuillez poster une source pour cela.
Ryan Stone
144

Les mysql_fonctions:

  1. sont obsolètes - ils ne sont plus maintenus
  2. ne vous permet pas de passer facilement à un autre backend de base de données
  3. ne supporte pas les déclarations préparées, donc
  4. encourager les programmeurs à utiliser la concaténation pour créer des requêtes, conduisant à des vulnérabilités d'injection SQL
Alnitak
la source
18
# 2 est également vrai demysqli_
Eggyal
16
pour être juste, étant donné les variations dans le dialecte SQL, même PDO ne vous donne pas # 2 avec un certain degré de certitude. Vous auriez besoin d'un emballage ORM approprié pour cela.
SDC
la mysql_*fonction est un shell dans les fonctions mysqlnd pour les nouvelles versions de PHP. Donc même si l'ancienne bibliothèque client n'est plus maintenue, mysqlnd est maintenu :)
hakre
Le problème n'est pas que de nombreux fournisseurs d'hébergement Web peuvent prendre en charge un tel style de conception orienté objet en raison d'une version php obsolète
Raju yourPepe
@RajuGujarati alors trouvez un hébergeur qui le peut. Si votre hébergeur ne le fait pas, il y a de fortes chances qu'il soit vulnérable aux attaques sur ses serveurs.
Alnitak
106

En parlant de raisons techniques , il n'y en a que quelques-unes, extrêmement spécifiques et rarement utilisées. Très probablement, vous ne les utiliserez jamais dans votre vie.
Je suis peut-être trop ignorant, mais je n'ai jamais eu l'occasion de les utiliser comme

  • requêtes asynchrones non bloquantes
  • procédures stockées renvoyant plusieurs jeux de résultats
  • Cryptage (SSL)
  • Compression

Si vous en avez besoin - ce sont sans aucun doute des raisons techniques pour passer de l'extension mysql à quelque chose de plus élégant et moderne.

Néanmoins, il existe également des problèmes non techniques, qui peuvent rendre votre expérience un peu plus difficile

  • une utilisation plus poussée de ces fonctions avec les versions modernes de PHP soulèvera des notifications de niveau obsolète. Ils peuvent simplement être désactivés.
  • dans un avenir lointain, ils peuvent éventuellement être supprimés de la version PHP par défaut. Ce n'est pas grave non plus, car mydsql ext sera déplacé dans PECL et chaque hébergeur sera heureux de compiler PHP avec, car ils ne veulent pas perdre de clients dont les sites fonctionnent depuis des décennies.
  • forte résistance de la communauté Stackoverflow. Time chaque fois que vous mentionnez ces fonctions honnêtes, on vous dit qu'elles sont soumises à un tabou strict.
  • étant un utilisateur PHP moyen, votre idée d'utiliser ces fonctions est probablement sujette aux erreurs et erronée. Juste à cause de tous ces nombreux tutoriels et manuels qui vous enseignent la mauvaise façon. Pas les fonctions elles-mêmes - je dois le souligner - mais la façon dont elles sont utilisées.

Ce dernier problème est un problème.
Mais, à mon avis, la solution proposée n'est pas meilleure non plus.
Il me semble un rêve trop idéaliste que tous ces utilisateurs PHP apprennent à gérer correctement les requêtes SQL à la fois. Très probablement, ils changeraient simplement mysql_ * en mysqli_ * mécaniquement, laissant l'approche la même . Surtout parce que mysqli rend l'utilisation des instructions préparées incroyablement douloureuse et gênante.
Sans oublier que les instructions natives préparées ne suffisent pas à protéger des injections SQL, et ni mysqli ni PDO ne proposent de solution.

Donc, au lieu de lutter contre cette extension honnête, je préfère lutter contre les mauvaises pratiques et éduquer les gens de la bonne manière.

En outre, il existe des raisons fausses ou non significatives, comme

  • Ne prend pas en charge les procédures stockées (nous utilisions mysql_query("CALL my_proc");depuis des siècles)
  • Ne prend pas en charge les transactions (comme ci-dessus)
  • Ne prend pas en charge les déclarations multiples (qui en a besoin?)
  • Pas en développement actif (alors quoi? Cela vous affecte-t- il manière pratique?)
  • Manque une interface OO (pour en créer une, c'est une question de plusieurs heures)
  • Ne prend pas en charge les instructions préparées ou les requêtes paramétrées

Le dernier est un point intéressant. Bien que mysql ext ne prenne pas en charge les instructions natives préparées, elles ne sont pas requises pour la sécurité. Nous pouvons facilement simuler des instructions préparées à l'aide d'espaces réservés gérés manuellement (comme le fait PDO):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

voila est , tout est paramétré et sûr.

Mais bon, si vous n'aimez pas la case rouge dans le manuel, un problème de choix se pose: mysqli ou PDO?

Eh bien, la réponse serait la suivante:

  • Si vous comprenez la nécessité d'utiliser une couche d'abstraction de base de données et de rechercher une API pour en créer une, mysqli est un très bon choix, car il prend en charge de nombreuses fonctionnalités spécifiques à mysql.
  • Si, comme la grande majorité des utilisateurs de PHP, vous utilisez des appels d'API bruts directement dans le code d'application (ce qui est essentiellement une mauvaise pratique) - PDO est le seul choix , car cette extension prétend être non seulement une API, mais plutôt un semi-DAL, encore incomplet mais offre de nombreuses fonctionnalités importantes, avec deux d'entre elles, PDO se distingue de manière critique de mysqli:

    • contrairement à mysqli, PDO peut lier des espaces réservés par valeur , ce qui rend les requêtes construites dynamiquement réalisables sans plusieurs écrans de code assez compliqué.
    • contrairement à mysqli, PDO peut toujours retourner le résultat de la requête dans un tableau simple et habituel, tandis que mysqli ne peut le faire que sur les installations mysqlnd.

Donc, si vous êtes un utilisateur PHP moyen et que vous souhaitez vous épargner une tonne de maux de tête lors de l'utilisation d'instructions préparées nativement, PDO - encore une fois - est le seul choix.
Cependant, l'AOP n'est pas non plus une solution miracle et a ses difficultés.
J'ai donc écrit des solutions pour tous les pièges courants et les cas complexes dans le wiki des balises PDO

Néanmoins, tous ceux qui parlent d'extensions manquent toujours les 2 faits importants sur Mysqli et PDO:

  1. La déclaration préparée n'est pas une solution miracle . Il existe des identificateurs dynamiques qui ne peuvent pas être liés à l'aide d'instructions préparées. Il existe des requêtes dynamiques avec un nombre inconnu de paramètres, ce qui rend la création de requêtes difficile.

  2. Ni mysqli_ * ni les fonctions PDO n'auraient dû apparaître dans le code de l'application.
    Il devrait y avoir une couche d'abstraction entre eux et le code d'application, qui fera tout le sale boulot de liaison, de boucle, de gestion des erreurs, etc. à l'intérieur, rendant le code d'application SEC et propre. Surtout pour les cas complexes comme la construction de requêtes dynamiques.

Donc, il ne suffit pas de passer à PDO ou à mysqli. Il faut utiliser un ORM, ou un générateur de requêtes, ou n'importe quelle classe d'abstraction de base de données au lieu d'appeler des fonctions API brutes dans leur code.
Et au contraire - si vous avez une couche d'abstraction entre votre code d'application et l'API mysql - peu importe le moteur utilisé. Vous pouvez utiliser mysql ext jusqu'à ce qu'il devienne obsolète, puis réécrire facilement votre classe d'abstraction sur un autre moteur, en conservant l'intégralité du code d'application.

Voici quelques exemples basés sur ma classe safemysql pour montrer comment une telle classe d'abstraction devrait être:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

Comparez cette seule ligne avec la quantité de code dont vous aurez besoin avec PDO .
Ensuite, comparez avec la quantité folle de code dont vous aurez besoin avec les instructions brutes préparées par Mysqli. Notez que la gestion des erreurs, le profilage, la journalisation des requêtes sont déjà intégrés et en cours d'exécution.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

Comparez-le avec les insertions PDO habituelles, lorsque chaque nom de champ est répété six à dix fois - dans tous ces nombreux espaces réservés nommés, liaisons et définitions de requête.

Un autre exemple:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Vous pouvez difficilement trouver un exemple pour PDO pour gérer un tel cas pratique.
Et ce sera trop verbeux et très probablement dangereux.

Donc, une fois de plus - ce n'est pas seulement le pilote brut qui devrait être votre préoccupation, mais la classe d'abstraction, utile non seulement pour des exemples idiots du manuel du débutant, mais pour résoudre tous les problèmes réels.

Votre bon sens
la source
20
mysql_*rend les vulnérabilités très faciles à trouver. Étant donné que PHP est utilisé par un grand nombre d'utilisateurs novices, il mysql_*est activement nuisible dans la pratique, même si en théorie il peut être utilisé sans accroc.
Madara's Ghost
4
everything is parameterized and safe- il peut être paramétré, mais votre fonction n'utilise pas de véritables instructions préparées.
uınbɐɥs
6
Comment est Not under active developmentseulement pour ce «0,01%» composé? Si vous construisez quelque chose avec cette fonction d'arrêt, mettez à jour votre version mysql dans un an et que vous vous retrouvez avec un système qui ne fonctionne pas, je suis sûr qu'il y a énormément de gens soudainement dans ce `` 0,01% ''. Je dirais cela deprecatedet not under active developmentsont étroitement liés. Vous pouvez dire qu'il n'y a "aucune raison [valable]", mais le fait est que lorsqu'on propose un choix entre les options, no active developmentest presque aussi mauvais que deprecatedje dirais?
Nanne
1
@MadaraUchiha: Pouvez-vous expliquer comment les vulnérabilités sont très faciles à trouver? Surtout dans les cas où ces mêmes vulnérabilités n'affectent pas PDO ou MySQLi ... Parce que je n'en connais pas une seule dont vous parlez.
ircmaxell
4
@ShaquinTrifonoff: bien sûr, il n'utilise pas d'instructions préparées. Mais pas plus que PDO , que la plupart des gens recommandent sur MySQLi. Je ne suis donc pas sûr que cela ait un impact significatif ici. Le code ci-dessus (avec un peu plus d'analyse) est ce que fait PDO lorsque vous préparez une instruction par défaut ...
ircmaxell
97

Il existe de nombreuses raisons, mais la plus importante est peut-être que ces fonctions encouragent les pratiques de programmation non sécurisées car elles ne prennent pas en charge les instructions préparées. Les instructions préparées aident à prévenir les attaques par injection SQL.

Lorsque vous utilisez des mysql_*fonctions, vous devez vous rappeler d'exécuter les paramètres fournis par l'utilisateur viamysql_real_escape_string() . Si vous oubliez en un seul endroit ou si vous n'échappez qu'une partie de l'entrée, votre base de données peut être attaquée.

L'utilisation d'instructions préparées dans PDOou mysqlirendra ce type d'erreurs de programmation plus difficile à faire.

Trott
la source
3
Malheureusement, la mauvaise prise en charge dans MySQLi_ * pour passer un nombre variable de paramètres (comme lorsque vous voulez passer une liste de valeurs à vérifier dans une clause IN) encourage la non-utilisation de paramètres, encourageant l'utilisation d'exactement les mêmes requêtes concaténées qui laisser les appels MySQL_ * vulnérables.
Kickstart
5
Mais, encore une fois, l'insécurité n'est pas un problème inhérent aux fonctions mysql_ *, mais un problème d'utilisation incorrecte.
Agamemnus
2
@Agamemnus Le problème est que mysql_ * facilite l'implémentation de cette "utilisation incorrecte", en particulier pour les programmeurs inexpérimentés. Les bibliothèques qui implémentent des instructions préparées rendent plus difficile de faire ce type d'erreur.
Trott
75

Parce que (entre autres raisons), il est beaucoup plus difficile de s'assurer que les données d'entrée sont nettoyées. Si vous utilisez des requêtes paramétrées, comme c'est le cas avec PDO ou mysqli, vous pouvez entièrement éviter le risque.

Par exemple, quelqu'un pourrait utiliser "enhzflep); drop table users" comme nom d'utilisateur. Les anciennes fonctions permettront d'exécuter plusieurs instructions par requête, donc quelque chose comme ce sale bougre peut supprimer une table entière.

Si l'on devait utiliser PDO de mysqli, le nom d'utilisateur finirait par être "enhzflep); drop table users" .

Voir bobby-tables.com .

enhzflep
la source
10
The old functions will allow executing of multiple statements per query- non, ils ne le feront pas. Ce type d'injection n'est pas possible avec ext / mysql - la seule façon dont ce type d'injection est possible avec PHP et MySQL est lors de l'utilisation de MySQLi et de la mysqli_multi_query()fonction. L'injection aimable qui est possible avec ext / mysql et des chaînes non échappées est des choses comme ' OR '1' = '1extraire des données de la base de données qui n'étaient pas censées être accessibles. Dans certaines situations, il est possible d'injecter des sous-requêtes, mais il n'est toujours pas possible de modifier la base de données de cette manière.
DaveRandom
64

Cette réponse est écrite pour montrer à quel point il est trivial de contourner le code de validation utilisateur PHP mal écrit, comment (et en utilisant quoi) ces attaques fonctionnent et comment remplacer les anciennes fonctions MySQL par une déclaration sécurisée préparée - et, fondamentalement, pourquoi les utilisateurs de StackOverflow (probablement avec beaucoup de représentants) aboient de nouveaux utilisateurs posant des questions pour améliorer leur code.

Tout d'abord, n'hésitez pas à créer cette base de données de test mysql (j'ai appelé mine prep):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

Cela fait, nous pouvons passer à notre code PHP.

Supposons que le script suivant soit le processus de vérification pour un administrateur sur un site Web (simplifié mais fonctionne si vous le copiez et l'utilisez pour les tests):

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Semble assez légitime à première vue.

L'utilisateur doit saisir un identifiant et un mot de passe, non?

Brillant, ne saisissez pas ce qui suit:

user: bob
pass: somePass

et le soumettre.

La sortie est la suivante:

You could not be verified. Please try again...

Super! Fonctionnant comme prévu, essayons maintenant le nom d'utilisateur et le mot de passe réels:

user: Fluffeh
pass: mypass

Incroyable! Salut tous les cinq, le code a correctement vérifié un administrateur. C'est parfait!

Eh bien pas vraiment. Disons que l'utilisateur est une petite personne intelligente. Disons que la personne est moi.

Entrez les informations suivantes:

user: bob
pass: n' or 1=1 or 'm=m

Et la sortie est:

The check passed. We have a verified admin!

Félicitations, vous venez de m'autoriser à entrer dans votre section réservée aux administrateurs super-protégés avec moi en entrant un faux nom d'utilisateur et un faux mot de passe. Sérieusement, si vous ne me croyez pas, créez la base de données avec le code que j'ai fourni, et exécutez ce code PHP - qui à première vue semble vraiment vérifier le nom d'utilisateur et le mot de passe plutôt bien.

Donc, en réponse, c'est pourquoi vous êtes crié à.

Alors, jetons un coup d'œil à ce qui n'a pas fonctionné et pourquoi je viens d'entrer dans votre grotte de super-administrateurs uniquement. J'ai pris une supposition et j'ai supposé que vous ne faisiez pas attention à vos entrées et les ai simplement transmises directement à la base de données. J'ai construit l'entrée de manière à MODIFIER la requête que vous exécutiez réellement. Alors, qu'est-ce que c'était censé être, et qu'est-ce que cela a fini par être?

select id, userid, pass from users where userid='$user' and pass='$pass'

C'est la requête, mais lorsque nous remplaçons les variables par les entrées réelles que nous avons utilisées, nous obtenons ce qui suit:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Voyez comment j'ai construit mon «mot de passe» pour qu'il ferme d'abord le guillemet simple autour du mot de passe, puis introduise une toute nouvelle comparaison? Ensuite, juste pour des raisons de sécurité, j'ai ajouté une autre "chaîne" afin que le guillemet simple soit fermé comme prévu dans le code que nous avions à l'origine.

Cependant, il ne s'agit pas de vous crier dessus maintenant, il s'agit de vous montrer comment sécuriser votre code.

D'accord, alors qu'est-ce qui a mal tourné, et comment pouvons-nous y remédier?

Il s'agit d'une attaque par injection SQL classique. L'un des plus simples d'ailleurs. À l'échelle des vecteurs d'attaque, il s'agit d'un tout-petit qui attaque un tank - et gagne.

Alors, comment pouvons-nous protéger votre section d'administration sacrée et la rendre agréable et sécurisée? La première chose à faire sera de cesser d'utiliser ceux qui sont vraiment anciens et obsolètesmysql_* . Je sais, vous avez suivi un tutoriel que vous avez trouvé en ligne et ça marche, mais c'est vieux, c'est dépassé et en l'espace de quelques minutes, je viens de le dépasser sans même transpirer.

Maintenant, vous avez les meilleures options pour utiliser mysqli_ ou PDO . Je suis personnellement un grand fan de PDO, donc j'utiliserai PDO dans le reste de cette réponse. Il y a des avantages et des inconvénients, mais personnellement, je trouve que les avantages l'emportent de loin sur les inconvénients. Il est portable sur plusieurs moteurs de base de données - que vous utilisiez MySQL ou Oracle ou à peu près n'importe quoi - juste en changeant la chaîne de connexion, il possède toutes les fonctionnalités de fantaisie que nous voulons utiliser et il est agréable et propre. J'aime propre.

Maintenant, regardons à nouveau ce code, cette fois écrit à l'aide d'un objet PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Les principales différences sont qu'il n'y a plus de mysql_*fonctions. Tout se fait via un objet PDO, deuxièmement, il utilise une instruction préparée. Maintenant, quelle est la déclaration préparatoire que vous demandez? C'est un moyen de dire à la base de données avant d'exécuter une requête, quelle est la requête que nous allons exécuter. Dans ce cas, nous disons à la base de données: "Salut, je vais exécuter une instruction select voulant id, userid et passer à partir des utilisateurs de la table où l'ID utilisateur est une variable et la passe est également une variable.".

Ensuite, dans l'instruction execute, nous transmettons à la base de données un tableau avec toutes les variables qu'elle attend maintenant.

Les résultats sont fantastiques. Essayons à nouveau ces combinaisons de nom d'utilisateur et de mot de passe:

user: bob
pass: somePass

L'utilisateur n'a pas été vérifié. Impressionnant.

Que diriez-vous:

user: Fluffeh
pass: mypass

Oh, je suis juste un peu excité, ça a marché: le chèque a réussi. Nous avons un administrateur vérifié!

Maintenant, essayons les données qu'un type intelligent entrerait pour essayer de dépasser notre petit système de vérification:

user: bob
pass: n' or 1=1 or 'm=m

Cette fois, nous obtenons ce qui suit:

You could not be verified. Please try again...

C'est pourquoi vous êtes crié lors de la publication de questions - c'est parce que les gens peuvent voir que votre code peut être contourné sans même essayer. Veuillez utiliser cette question et réponse pour améliorer votre code, le rendre plus sûr et utiliser les fonctions actuelles.

Enfin, cela ne veut pas dire que c'est du code PARFAIT. Il y a beaucoup d'autres choses que vous pourriez faire pour l'améliorer, utilisez des mots de passe hachés par exemple, assurez-vous que lorsque vous stockez des informations sensibles dans la base de données, vous ne les stockez pas en texte brut, avez plusieurs niveaux de vérification - mais vraiment, si vous changez simplement votre ancien code propice à l'injection en ceci, vous serez BIEN en train d'écrire du bon code - et le fait que vous soyez arrivé jusqu'ici et que vous lisez encore me donne un espoir que vous n'implémenterez pas seulement ce type de code lors de l'écriture de vos sites Web et applications, mais que vous puissiez sortir et rechercher ces autres choses que je viens de mentionner - et plus encore. Écrivez le meilleur code possible, pas le code le plus basique qui fonctionne à peine.

Fluffeh
la source
2
Merci pour votre réponse! Ayez mon +1! Il convient de noter qu'en mysql_*soi, ce n'est pas dangereux, mais cela promeut le code non sécurisé via de mauvais didacticiels et l'absence d'une API de préparation de déclaration appropriée.
Madara's Ghost
2
mots de passe non hachés, oh l'horreur! = oP Sinon +1 pour une explication détaillée.
cryptique
33

L'extension MySQL est la plus ancienne des trois et était la manière originale utilisée par les développeurs pour communiquer avec MySQL. Cette extension est désormais déconseillée au profit des deux autres alternatives en raison des améliorations apportées aux nouvelles versions de PHP et de MySQL.

  • MySQLi est l'extension «améliorée» pour travailler avec les bases de données MySQL. Il tire parti des fonctionnalités disponibles dans les versions plus récentes du serveur MySQL, expose à la fois une interface orientée fonction et une interface orientée objet au développeur et fait quelques autres astuces.

  • PDO propose une API qui consolide la plupart des fonctionnalités qui étaient auparavant réparties sur les principales extensions d'accès à la base de données, à savoir MySQL, PostgreSQL, SQLite, MSSQL, etc. L'interface expose des objets de haut niveau pour que le programmeur puisse travailler avec les connexions, les requêtes et les les ensembles de résultats et les pilotes de bas niveau effectuent la communication et la gestion des ressources avec le serveur de base de données. Beaucoup de discussions et de travaux sont en cours dans PDO et il est considéré comme la méthode appropriée pour travailler avec des bases de données dans un code moderne et professionnel.

Alexandre
la source
21

Je trouve les réponses ci-dessus très longues, donc pour résumer:

L'extension mysqli présente un certain nombre d'avantages, les principales améliorations par rapport à l'extension mysql étant:

  • Interface orientée objet
  • Prise en charge des déclarations préparées
  • Prise en charge de plusieurs instructions
  • Prise en charge des transactions
  • Capacités de débogage améliorées
  • Prise en charge du serveur intégré

Source: Présentation de MySQLi


Comme expliqué dans les réponses ci-dessus, les alternatives à mysql sont mysqli et PDO (PHP Data Objects).

  • L'API prend en charge les instructions préparées côté serveur: prises en charge par MYSQLi et PDO
  • L'API prend en charge les instructions préparées côté client: prises en charge uniquement par PDO
  • L'API prend en charge les procédures stockées: MySQLi et PDO
  • L'API prend en charge plusieurs instructions et toutes les fonctionnalités de MySQL 4.1+ - Pris en charge par MySQLi et principalement par PDO

MySQLi et PDO ont été introduits dans PHP 5.0, tandis que MySQL a été introduit avant PHP 3.0. Un point à noter est que MySQL est inclus dans PHP5.x bien que déconseillé dans les versions ultérieures.

Ani Menon
la source
2
Votre réponse est trop longue, alors que le vrai résumé est "mysql ext is no more". C'est tout
Your Common Sense
1
@YourCommonSense Ma réponse est de savoir pourquoi mysqli a remplacé mysql. Il ne s'agit pas de dire que Mysqli existe aujourd'hui, alors utilisez-le .. Tout le monde le sait!
Ani Menon
1
Eh bien, à part le fait que personne ne demande pourquoi mysqli a remplacé mysql, cela ne répond pas non plus à cette question. Cela explique pourquoi mysqli a été introduit. Mais cela n'explique pas pourquoi mysql et mysqli n'étaient pas autorisés à vivre en parallèle
Your Common Sense
@YourCommonSense La question du PO est également "Pourquoi devrais-je utiliser autre chose même s'ils fonctionnent sur mon site?" et c'est la raison pour laquelle j'ai souligné les changements et améliorations. Vous pouvez regarder toutes les autres réponses, elles sont longues, alors j'ai pensé que je devais la résumer.
Ani Menon
6

Il est possible de définir presque toutes les mysql_*fonctions en utilisant mysqli ou PDO. Il suffit de les inclure au-dessus de votre ancienne application PHP, et cela fonctionnera sur PHP7. Ma solution ici .

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}
Pavel Tzonkov
la source
Au lieu d'afficher un lien pour votre solution, veuillez les ajouter ici comme réponse.
amarnath
1

Les fonctions similaires à celle mysql_connect()-ci mysql_query()sont les fonctions PHP ie (PHP 4) de la version précédente et ne sont plus utilisées.

Celles-ci sont remplacées par mysqli_connect(), de mysqli_query()même dans la dernière version de PHP5.

C'est la raison de l'erreur.

Tueur
la source
2
PHP 5 n'est pas le dernier depuis plus de 2 ans maintenant.
Madara's Ghost
1

MySQL déconseillé en PHP 5.5.0, et supprimé en PHP 7.0.0. Pour une grande et ancienne application, il est difficile de rechercher et de remplacer chaque fonction.

Nous pouvons utiliser les fonctions MySQL en créant une fonction wrapper pour chacune ci-dessous exécute du code. Cliquez ici

Vin
la source
-9

Les fonctions mysql_ * ont été dépréciées (à partir de PHP 5.5 ) étant donné que de meilleures fonctions et structures de code ont été développées. Le fait que la fonction soit obsolète signifie que plus aucun effort ne sera fait pour l'améliorer en termes de performances et de sécurité, ce qui signifie qu'elle est moins à l'épreuve du temps .

Si vous avez besoin de plus de raisons:

  • Les fonctions mysql_ * ne prennent pas en charge les instructions préparées.
  • Les fonctions mysql_ * ne prennent pas en charge la liaison de paramètres.
  • Les fonctions mysql_ * manquent de fonctionnalités pour la programmation orientée objet.
  • la liste continue ...
Webeng
la source
18
Cette réponse est dépassée. De plus, cela n'ajoute rien d'utile aux réponses qui existent déjà.
Votre bon sens