2723 votes

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

Quelles sont les raisons techniques pour lesquelles on ne devrait pas utiliser mysql_* fonctions ? (par exemple mysql_query() , mysql_connect() o mysql_real_escape_string() )?

Pourquoi devrais-je utiliser autre chose, même si cela fonctionne sur mon site ?

S'ils ne fonctionnent pas sur mon site, pourquoi j'obtiens des erreurs telles que

Attention : mysql_connect() : Aucun fichier ou répertoire de ce type

3 votes

L'erreur doit être comme : Erreur fatale : Unecaught Error : Appel à une fonction non définie mysql_connect() ...

48 votes

Le fait qu'ils soient dépréciés est une raison suffisante pour les éviter.

116voto

En parlant de technique Il n'y a que quelques raisons, extrêmement spécifiques et rarement utilisées. Il est fort probable que vous ne les utiliserez jamais dans votre vie.
Peut-être que je suis trop ignorant, mais je n'ai jamais eu l'occasion d'utiliser ces choses comme

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

Si vous en avez besoin, il s'agit sans doute de raisons techniques pour délaisser l'extension mysql au profit de quelque chose de plus élégant et de plus moderne.

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

  • La poursuite de l'utilisation de ces fonctions avec les versions modernes de PHP fera apparaître des avis de niveau déprécié. Elles peuvent simplement être désactivées.
  • dans un futur lointain, ils peuvent être éventuellement retirés de la compilation par défaut de PHP. Ce n'est pas grave, car mydsql ext sera déplacé dans PECL et tous les hébergeurs seront heureux de compiler PHP avec, car ils ne veulent pas perdre des clients dont les sites fonctionnaient depuis des décennies.
  • Une forte résistance de la part de la communauté Stackoverflow. Chaque fois que vous mentionnez ces fonctions honnêtes, on vous dit qu'elles sont strictement taboues.
  • Si vous êtes un utilisateur moyen de PHP, il est fort probable que votre conception de l'utilisation de ces fonctions soit erronée et sujette aux erreurs. Tout simplement à cause de tous ces nombreux tutoriels et manuels qui vous enseignent la mauvaise méthode. Pas les fonctions elles-mêmes - je dois le souligner - mais la façon dont elles sont utilisées.

Cette dernière question est un problème.
Mais, à mon avis, la solution proposée n'est pas meilleure non plus.
Il me semble que trop idéaliste un rêve que tous ces utilisateurs de PHP apprennent d'un coup à gérer correctement les requêtes SQL. Le plus probable est qu'ils se contentent de remplacer mécaniquement mysql_* par mysqli_*, en laissant la même approche . Surtout parce que mysqli rend l'utilisation des instructions préparées incroyablement pénible et gênante.
Sans oublier que indigène déclarations préparées ne sont pas suffisantes pour protéger contre les injections SQL, et ni mysqli ni PDO n'offrent de solution.

Ainsi, au lieu de lutter contre cette extension honnête, je préfère combattre les mauvaises pratiques et éduquer les gens aux bonnes méthodes.

Il existe également des raisons fausses ou non significatives, telles que

  • Ne supporte pas les procédures stockées (nous utilisions mysql_query("CALL my_proc"); pour les âges)
  • Ne supporte pas les transactions (idem ci-dessus)
  • Ne prend pas en charge les déclarations multiples (qui en a besoin ?)
  • Pas en cours de développement actif (et alors ? cela affecte-t-il vous d'une manière pratique ?)
  • Manque d'une interface OO (en créer une est l'affaire de plusieurs heures)
  • Ne prend pas en charge les instructions préparées ou les requêtes paramétrées.

Le dernier point est intéressant. Bien que mysql ext ne supporte pas indigène les déclarations préparées, elles ne sont pas nécessaires pour la sécurité. Nous pouvons facilement simuler les instructions préparées en utilisant des placeholders gérés manuellement (tout comme PDO le fait) :

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 tout est paramétré et sécurisé.

Mais bon, si vous n'aimez pas la boîte rouge du 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 un couche d'abstraction de base de données et à la recherche d'une API pour en créer une, mysqli est un très bon choix, car il supporte en effet 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 de l'application (ce qui n'est pas une bonne pratique), vous pouvez utiliser la méthode de l'API brute. L'AOP est le seul choix possible Cette extension prétend ne pas être une simple API mais plutôt un semi-DAL, encore incomplet mais offrant de nombreuses fonctionnalités importantes, dont deux font que PDO se distingue nettement de mysqli :

    • à la différence de mysqli, PDO peut lier des caractères de remplacement par valeur qui permet d'élaborer des requêtes de manière dynamique sans avoir recours à plusieurs écrans de code compliqué.
    • Contrairement à mysqli, PDO peut toujours retourner le résultat d'une requête dans un simple tableau habituel, alors que mysqli ne peut le faire que sur les installations mysqlnd.

Ainsi, si vous êtes un utilisateur PHP moyen et que vous voulez vous épargner une tonne de maux de tête lors de l'utilisation d'instructions préparées natives, PDO - encore une fois - est le seul choix possible.
Cependant, l'AOP n'est pas non plus une solution miracle et comporte des difficultés.
C'est pourquoi j'ai rédigé des solutions pour tous les pièges courants et les cas complexes dans le document Wiki des balises PDO

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

  1. Déclaration préparée n'est pas une solution miracle . Il existe des identifiants 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 construction de requêtes difficile.

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

Il ne suffit donc pas de passer à PDO ou à mysqli. Il faut utiliser un ORM, ou un constructeur de requêtes, ou toute autre classe d'abstraction de base de données au lieu d'appeler les fonctions API brutes dans son code.
Et au contraire - si vous avez une couche d'abstraction entre votre code d'application et l'API mysql - le moteur utilisé n'a pas d'importance. Vous pouvez utiliser mysql ext jusqu'à ce qu'il soit déprécié, puis réécrire facilement votre classe d'abstraction pour un autre moteur, en ayant tout le code de l'application intact.

Voici quelques exemples basés sur mes 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 quantité de code dont vous aurez besoin avec PDO .
Comparez ensuite avec une quantité folle de code dont vous aurez besoin avec les déclarations préparées brutes Mysqli. Notez que la gestion des erreurs, le profilage et la journalisation des requêtes sont déjà intégrés et fonctionnent.

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

Comparez cela aux insertions PDO habituelles, où chaque nom de champ est répété six à dix fois - dans tous ces nombreux espaces réservés, liens et définitions de requêtes.

Un autre exemple :

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

Vous pouvez difficilement trouver un exemple de 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 doit vous préoccuper mais la classe d'abstraction, utile non seulement pour les exemples idiots du manuel du débutant mais pour résoudre n'importe quel problème de la vie réelle.

106voto

Trott Points 16299

Les raisons sont nombreuses, 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 déclarations préparées. Les instructions préparées aident à prévenir les attaques par injection SQL.

Lorsque vous utilisez mysql_* vous devez vous souvenir de faire passer les paramètres fournis par l'utilisateur par les fonctions mysql_real_escape_string() . Si vous oubliez à un seul endroit ou si vous n'échappez qu'à une partie de la saisie, votre base de données peut faire l'objet d'une attaque.

Utilisation des déclarations préparées dans PDO o mysqli fera en sorte que ce genre d'erreurs de programmation soit plus difficile à commettre.

79voto

enhzflep Points 5549

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 on le fait avec PDO ou mysqli, vous pouvez totalement éviter ce risque.

Par exemple, on pourrait utiliser "enhzflep); drop table users" comme nom d'utilisateur. Les anciennes fonctions permettent d'exécuter plusieurs instructions par requête, de sorte que quelque chose comme ce méchant bougre peut supprimer une table entière.

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

Voir bobby-tables.com .

76voto

Fluffeh Points 21893

Cette réponse est écrite pour montrer à quel point il est trivial de contourner un code de validation utilisateur PHP mal écrit, comment (et à l'aide de quoi) ces attaques fonctionnent et comment remplacer les anciennes fonctions MySQL par une instruction préparée sécurisée - et, en gros, pourquoi les utilisateurs de StackOverflow (qui ont probablement beaucoup de réputation) aboient sur les nouveaux utilisateurs qui posent des questions pour améliorer leur code.

Tout d'abord, n'hésitez pas à créer cette base de données mysql de test (j'ai appelé la mienne 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)

Ceci étant fait, nous pouvons passer à notre code PHP.

Supposons que le script suivant soit le processus de vérification d'un administrateur sur un site web (simplifié mais fonctionnel si vous le copiez et l'utilisez pour le tester) :

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

Ça semble assez légitime à première vue.

L'utilisateur doit entrer un login et un mot de passe, non ?

Brillant, ne pas entrer dans ce qui suit :

user: bob
pass: somePass

et le soumettre.

Le résultat est le suivant :

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

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

user: Fluffeh
pass: mypass

Incroyable ! Hi-fives tout autour, 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 cette personne, c'est moi.

Entrez ce qui suit :

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

Et le résultat est :

The check passed. We have a verified admin!

Félicitations, vous venez de me permettre d'entrer dans votre section super-protégée réservée aux admins 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 vérifier le nom d'utilisateur et le mot de passe plutôt bien.

Donc, en réponse, C'EST POURQUOI ON VOUS CRIE dessus.

Voyons donc ce qui s'est passé, et pourquoi je suis entré dans votre caverne de chauve-souris réservée aux super-administrateurs. J'ai supposé que vous ne faisiez pas attention à vos entrées et que vous les transmettiez directement à la base de données. J'ai construit l'entrée d'une manière qui changerait la requête que vous exécutez réellement. Alors, qu'est-ce que c'était censé être, et qu'est-ce que ça 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'

Vous voyez comment j'ai construit mon "mot de passe" pour qu'il ferme d'abord le guillemet simple autour du mot de passe, puis qu'il introduise une toute nouvelle comparaison ? Puis, par sécurité, j'ai ajouté une autre "chaîne" pour que le guillemet simple soit fermé comme prévu dans le code que nous avions initialement.

Cependant, il ne s'agit pas de vous crier dessus maintenant, mais de vous montrer comment rendre votre code plus sûr.

Ok, alors qu'est-ce qui a mal tourné, et comment on peut le réparer ?

Il s'agit d'une attaque classique par injection SQL. L'une des plus simples d'ailleurs. À l'échelle des vecteurs d'attaque, c'est un enfant qui attaque un char d'assaut - et qui gagne.

Alors, comment protéger votre section d'administration sacrée et la rendre agréable et sûre ? La première chose à faire est d'arrêter d'utiliser ces très vieux et dépréciés mysql_* fonctions. 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 l'ai dépassé sans même transpirer.

Maintenant, vous avez la possibilité d'utiliser mysqli_ o AOP . Je suis personnellement un grand fan de PDO, donc je vais utiliser 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 largement sur les inconvénients. Il est portable sur plusieurs moteurs de bases de données - que vous utilisiez MySQL ou Oracle ou n'importe quoi d'autre - simplement en changeant la chaîne de connexion, il a toutes les fonctionnalités fantaisistes que nous voulons utiliser et il est beau et propre. J'aime la propreté.

Maintenant, regardons à nouveau ce code, cette fois-ci écrit en utilisant 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 par l'intermédiaire d'un objet PDO. Ensuite, on utilise une instruction préparée. Maintenant, qu'est-ce qu'une instruction préparée, vous 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 : "Bonjour, je vais exécuter une commande select voulant id, userid et pass de la table users où le userid est une variable et le pass est aussi une variable.".

Ensuite, dans l'instruction d'exécution, nous transmettons à la base de données un tableau contenant 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é. Génial.

Pourquoi pas :

user: Fluffeh
pass: mypass

Oh, je me suis juste un peu excité, ça a marché : Le chèque est passé. Nous avons un administrateur vérifié !

Maintenant, essayons les données qu'un petit malin entrerait pour essayer de 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 on vous crie dessus lorsque vous posez des questions - c'est parce que les gens peuvent voir que votre code peut être contourné sans même essayer. S'il vous plaît, utilisez cette question et cette réponse pour améliorer votre code, pour le rendre plus sûr et pour utiliser des fonctions qui sont à jour.

Enfin, cela ne veut pas dire que ce code est PARFAIT. Il y a beaucoup d'autres choses que vous pourriez faire pour l'améliorer, utiliser des mots de passe hachés par exemple, vous assurer que lorsque vous stockez des informations sensibles dans la base de données, vous ne les stockez pas en texte clair, avoir plusieurs niveaux de vérification - mais vraiment, si vous changez simplement votre vieux code sujet aux injections par celui-ci, le fait que vous soyez arrivé jusqu'ici et que vous continuiez à lire me donne l'espoir que vous ne vous contenterez pas de mettre en œuvre ce type de code lorsque vous écrirez vos sites Web et vos applications, mais que vous ferez des recherches sur les autres points que je viens de mentionner, et plus encore. Écrivez le meilleur code possible, pas le code le plus basique qui fonctionne à peine.

39voto

Alexander Points 1624

L'extension MySQL est la plus ancienne des trois et était le moyen original utilisé par les développeurs pour communiquer avec MySQL. Cette extension est maintenant déprécié en faveur de l'autre deux alternatives en raison des améliorations apportées dans les nouvelles versions de PHP et de MySQL.

  • MySQLi est l'extension "améliorée" pour travailler avec les bases de données MySQL. Elle tire parti des fonctionnalités disponibles dans les nouvelles versions du serveur MySQL, expose au développeur une interface orientée fonction et une interface orientée objet, et fait quelques autres choses intéressantes.

  • AOP offre une API qui consolide la plupart des fonctionnalités qui étaient auparavant réparties entre les principales extensions d'accès aux bases de données, à savoir MySQL, PostgreSQL, SQLite, MSSQL, etc. L'interface expose des objets de haut niveau permettant au programmeur de travailler avec les connexions aux bases de données, les requêtes et les ensembles de résultats, tandis que les pilotes de bas niveau assurent la communication et la gestion des ressources avec le serveur de base de données. PDO fait l'objet de nombreuses discussions et travaux et est considéré comme la méthode appropriée pour travailler avec des bases de données dans un code moderne et professionnel.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X