Débat : Magic Quote pour ou contre ?

Débat : Magic Quote pour ou contre ? - PHP - Programmation

Marsh Posté le 16-02-2007 à 12:15:54    

Salut,
 
Etes vous pour on contre l'utilisation de magic quote ? notement pour les soucis d'injections SQL
Et si oui pourquoi ?

Reply

Marsh Posté le 16-02-2007 à 12:15:54   

Reply

Marsh Posté le 16-02-2007 à 12:20:15    

c'te vieux débat :o
Donc AMA, ça pue. Dans l'ordre préférer PDO ou mysqli, une librairie d'abstraction type adodb, au pire du pire sprintf.

Reply

Marsh Posté le 16-02-2007 à 12:30:23    

Pour l'instant jutilise mysql_real_escape_string() mais c'est super relou :(

Reply

Marsh Posté le 16-02-2007 à 14:22:42    

c'est de la merde en branche. il faut utiliser des requêtes paramétrées.

Reply

Marsh Posté le 16-02-2007 à 15:08:38    

MagicBuzz a écrit :

c'est de la merde en branche. il faut utiliser des requêtes paramétrées.


 
mais encore

Reply

Marsh Posté le 16-02-2007 à 15:18:10    


Des requêtes préparées. Tu as ça dans PDO (entre autres).


Message édité par skeye le 16-02-2007 à 15:20:33

---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 16-02-2007 à 15:19:26    

exemple de la doc:
 
Exemple 1830. Insertions répétitives en utilisant les requêtes préparées
 
Cet exemple effectue une requête INSERT en y substituant un nom et une valeur pour les marqueurs nommés.

Code :
  1. <?php
  2. $stmt = $dbh->prepare("INSERT INTO REGISTRY (nom, valeur) VALUES (:nom, :valeur)" );
  3. $stmt->bindParam(':nom', $nom);
  4. $stmt->bindParam(':valeur', $valeur);
  5.  
  6. // insertion d'une ligne
  7. $nom = 'one';
  8. $valeur = 1;
  9. $stmt->execute();
  10.  
  11. // insertion d'une autre ligne avec des valeurs différentes
  12. $nom = 'two';
  13. $valeur = 2;
  14. $stmt->execute();
  15. ?>


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 16-02-2007 à 16:19:45    

nycius a écrit :

Pour l'instant jutilise mysql_real_escape_string() mais c'est super relou :(


C'est peut-être relou, mais c'est la seule méthode valable avec l'extension mysql. magic_quotes va utiliser la fonction addslashes, qui ne quote pas comme il faut pour une base mysql, et ne tient pas compte du charset. Genre avec un charset multibyte genre GBKmachin (c'est pour du chinois), addslashes va "omettre" certains quotes, et donc ne va strictement rien faire *boom*.
 
Ensuite magic_quotes va quoter les valeurs, sauf qu'on n'a pas toujours besoin de ça : affichage dans un document XML, insertion dans un SGBD qui demande un escape différent, affichage dans un fichier texte, tous on des besoins différents... Donc à jeter définitivement.
 
Et c'est tellement nul, que la directive a été enlevée de PHP6. Donc autant prendre de suite les bonnes habitudes, et coder sans. On oubliera pas donc, en début de chaque script, nettoyer les superglobales genre $_GET, $_POST etc. si jamais magic_quotes_gpc est actif (grâce à stripslashes et get_magic_quotes_gpc)

Reply

Marsh Posté le 16-02-2007 à 16:23:54    

Exact, j'ai une class mysql dont je me sers tj, je vais essayer de placer directement dans la classe une solution comme ca pour eviter de passer sur chaque variable $_GET, $_POST un mysql_real_escape_string()

Reply

Marsh Posté le 16-02-2007 à 22:08:28    

MagicBuzz a écrit :

c'est de la merde en branche. il faut utiliser des requêtes paramétrées.

 

Tout ce que j'ai pu lire sur les requetes preparées indiquent qu'elles sont intéressantes lorsqu'une meme requete est appelée plusieurs ( avec ou sans parametres ). Pas dans tous les cas.  :hello:

 


Message édité par supermofo le 16-02-2007 à 22:08:51
Reply

Marsh Posté le 16-02-2007 à 22:08:28   

Reply

Marsh Posté le 16-02-2007 à 22:27:30    

si, elles sont intéressantes dans tous les cas parceque :
1/ facilement maintenable : le SQL est totalement séparé de la construction des paramètres
2/ PHP fait du pooling de connexion inter-sessions. c'est à dire qu'une même requête paramétrée peut être appelée par deux utilisateurs différents : du coup toute requête peut être considérée comme utilisée souvent, même si elle n'est présente qu'une fois dans une seule page
3/ c'est ce qui offre la protection la plus sûre contre le SQL Injection, et autres exploit des SGBD. ceci dit, l'implémentation d'ADO est encore meilleure, puisqu'elle oblige à typer les paramètres, ce qui permet de les valider avant d'effectuer la requête (avec cmd.Prepare()) ce qui permet notamment d'éviter un aller-retour avec le SGBD si la requête a des paramètres invalides.

Message cité 1 fois
Message édité par MagicBuzz le 16-02-2007 à 22:29:15
Reply

Marsh Posté le 17-02-2007 à 21:15:06    

MagicBuzz a écrit :

si, elles sont intéressantes dans tous les cas parceque :
1/ facilement maintenable : le SQL est totalement séparé de la construction des paramètres
2/ PHP fait du pooling de connexion inter-sessions. c'est à dire qu'une même requête paramétrée peut être appelée par deux utilisateurs différents : du coup toute requête peut être considérée comme utilisée souvent, même si elle n'est présente qu'une fois dans une seule page
3/ c'est ce qui offre la protection la plus sûre contre le SQL Injection, et autres exploit des SGBD. ceci dit, l'implémentation d'ADO est encore meilleure, puisqu'elle oblige à typer les paramètres, ce qui permet de les valider avant d'effectuer la requête (avec cmd.Prepare()) ce qui permet notamment d'éviter un aller-retour avec le SGBD si la requête a des paramètres invalides.


 
Tu veux dire que si tu fais une requete préparer il n'y as pas besoin de passer par du mysql_real_escape_string? Ou alors tu dois quand même le faire en plus ... Et dans ce cas je vois pas çe que ça change niveau securité


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 17-02-2007 à 21:31:26    

esox_ch a écrit :

Tu veux dire que si tu fais une requete préparer il n'y as pas besoin de passer par du mysql_real_escape_string? Ou alors tu dois quand même le faire en plus ... Et dans ce cas je vois pas çe que ça change niveau securité


normalement, c'est pas a toi de le faire, c'est l'objet qui gere les requetes qui le doit le faire quand tu lui passe une chaine de caractère ...

Reply

Marsh Posté le 17-02-2007 à 22:13:42    

esox_ch a écrit :

Tu veux dire que si tu fais une requete préparer il n'y as pas besoin de passer par du mysql_real_escape_string? Ou alors tu dois quand même le faire en plus ... Et dans ce cas je vois pas çe que ça change niveau securité


Non seulement tu n'as pas à le faire, mais surtout, si tu le fait, tu corromps les données.
 
En fait, une requête paramétrée est passée "telle quelle" au SGBD, c'est à dire avec le nom des variables genre ":valeur".
Et en même temps, dans une zone typée, la valeur de ta variable est envoyée. Ainsi, si ta variable de type "texte" contient des caractères "dangereux", alors il n'y a aucun souci, c'est bien la valeur qui est transmise au SGDB et non sa représentation littérale.
 
En gros, t'as cette requête :
 
select * from utilisateur where login = '$login'
 
Si $login contient la séquence suivante (SQL Injection) :
 
'; drop table utilisateur; --
 
Sans protection, on se retrouve avec ça :
 
select * from utilisateur where login = ''; drop table utilisateur; --'
 
=> Pas bien du tout :o
 
Avec MagicQuote tu te retrouves avec ça :
 
select * from utilisateur where login = '\'; drop table utilisateur; --'
 
=> A noter qu'avec SQL Server par exemple, proutch, aucune protection car le \ n'est pas du tout compté comme un caractère d'échappement.
 
Avec RealEscapeStringMachin :
 
select * from utilisateur where login = '''; drop table utilisateur; --'
 
=> La quote est correctement échappée, on effectue donc une recherche sur : (qui reste à échapper par le SGBD au moment de l'exécution)
 
''; drop table utilisateur
 
Avec les requêtes paramétrée, la requête exécutée est :
 
select * from utilisateur where login = :login
 
Plus une information disant que :login est de type texte et contient la valeur '; drop table utilisateur; --
 
Ce qui revient au même sauf que...
-> C'est la requête "sans la valeur" qui est réellement exécutée. Donc si la requête est utilisée régulièrement, même avec des valeurs différentes, le SGBD va pouvoir largement optimiser les performances de la requête car son plan d'ecution est déjà en mémoire (alors que si on passe par une requête littérale, le SGBD réanalyse tout à chaque fois qu'il y a le moindre caractère qui change dans la requête SQL)
-> Que le SGBD demande à échapper les quotes à la sauce ANSI ou à la sauce SQL, ou de n'importe quelle autre façon, on n'a aucun risque d'attaque, puisqu'on n'utilise pas d'échappement !
-> Mieux : si tu filtres sur un datetime ou un float, le problème récurent vient de la réprésentation de la date (DD/MM/YYYY, MM/DD/YYYY, YYYY-MM-DD, YYYYMMDD, ... ?) ou des problèmes de virgules, points et autres séparateurs décimaux, de milliers ou symboles monétaires, position du signe -... C'est source de tous les malheurs avec les systèmes "classiques". Avec une requête paramétrée, on s'en fout, c'est un entier, un float, un ce que tu veux, qui est directement envoyé depuis la représentation interne dans PHP vers ton SGBD. Ainsi tu peux avoir une appli écrite par un français qui va tourner sur un serveur chinois configuré en russe, t'as pas à te soucier de toute la merde classique liée aux locales pour le représentation des nombres et dates. Je ne parle pas non plus des caractères spéciaux qu'on trouve dans les charsets exotiques, qui peuvent être mal reconnus lors de l'échappement des quotes ASCII.
 
Bref, y'a pas plus sûr, et même si la syntaxe est un peu plus lourde, au final c'est bien moins chiant à utiliser.
 
En gros :
- sûr
- plus performant
- plus facile à maintenir
- évite les problèmes de localisation
 
Vraiment, c'est à se demander pourquoi tout le monde le boude (du moins personne ne connait).
Je viens de passer la semaine à corriger un site en C# truffée de conneries de requêtes littérales... Ah ben d'un coup tout le monde pouvait se servir de tout le site (parceque sinon, les gens plantaient certaines pages parceque ces pages étaient configurées pour travailler avec la locale du navigateur client... et tout le monde n'était pas configuré en américain). On a sécurisé le bignou, et le serveur Oracle s'est senti revivre quand les requêtes de 200 lignes sont devenues tout le temps pareilles plutôt que différentes à chaque exécution.
Bref, ce système fait ses preuves depuis 10 ans sur tous les langages, faut l'utiliser :o


Message édité par MagicBuzz le 17-02-2007 à 22:23:39
Reply

Marsh Posté le 17-02-2007 à 22:41:41    

Mais c'est associé à php que depuis la v5 il me semble, et MySql a mis longtemps à se mettre à jour sur cet aspect là aussi...
 
Par contre, ya pas à chier, ça dépote! Et ça rassure aussi, ce qui est important avec un langage aussi lâche que php...

Reply

Marsh Posté le 17-02-2007 à 23:09:14    

Merci magic pour cette explication ...
Je vais donc implementer ça dans mon CMS dès que j'aurai le temps :D

Reply

Marsh Posté le 17-02-2007 à 23:34:10    

Histoire de mettre en valeur à la fois le problème des "." et des ",", ainsi les performances, je viens d'écrire un petit programme tout con en C# qui tape dans SQL Server (ouais, c'est limite HS, mais c'est le principe qui compte, et c'est le même).
 
J'ai volontairement mis le test des requêtes paramétrées en premier, puisqu'il sera pénalisé par l'absence totale de cache dans la connexion (ceci dit c'est vrai seulement pour les premières requêtes, puisqu'ensuite le pooling fait son travail).
 
Le résultat est relativement édifiant... Non seulement on n'a pas besoin de gérer le coup des virgules pour une variable de type chaîne calculée à partir d'un float -me suis épaté tout seul sur ce coup- mais en plus environ trois fois plus rapide !
 
La table "test" contient :
- Une clé primaire organisée en cuslter "id" de type numeric(18,0)
- Un champ indexé "strval" de type nvarchar(50)
- Un champ indexé "nbrval" de type numeric(18,9)
La table contient 5000 lignes, où strval et nbrval contienent des nombres aléatoires compris entre 0 et 1 différents.
 

Code :
  1. using System;
  2. using System.Data;
  3. using System.Data.SqlClient;
  4. namespace SQLBench
  5. {
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             Console.WriteLine("Début du bench" );
  11.             for (int j = 1; j <= 5; j++)
  12.             {
  13.                 DateTime startTime = DateTime.Now;
  14.                 Console.WriteLine("Passe {0} commence à {1}", j, startTime.ToShortTimeString());
  15.                
  16.                 string cnxString = @"Data Source=(local);Initial Catalog=test;Integrated Security=True";
  17.                 long nbTotal;
  18.                 Console.BufferWidth = 120;
  19.                 Console.Write("Génération du jeu de test (1000 valeurs à rechercher)..." );
  20.                 float[] rnd = new float[1000];
  21.                 Random Rand = new Random(DateTime.Now.Millisecond);
  22.                 for (int i = 0, length = rnd.Length; i < length; i++)
  23.                 {
  24.                     rnd[i] = (float)Rand.NextDouble();
  25.                 }
  26.                 Console.WriteLine(" {0} millisecondes", ((TimeSpan)(DateTime.Now - startTime)).TotalMilliseconds);
  27.                 startTime = DateTime.Now;
  28.                 Console.Write("Etablissement de la connexion..." );
  29.                 SqlConnection cnx = new SqlConnection(cnxString);
  30.                 cnx.Open();
  31.                 SqlCommand cmd = cnx.CreateCommand();
  32.                 cmd.CommandType = CommandType.Text;
  33.                 Console.WriteLine(" {0} millisecondes", ((TimeSpan)(DateTime.Now - startTime)).TotalMilliseconds);
  34.                 cmd.CommandText = "select count(id) from test where strval like substring(cast(@nbrval as nvarchar(50)), 1, 4) + '%' or nbrval between @nbrval - .01 and @nbrval + .01";
  35.                 SqlParameter pNbr = cmd.CreateParameter();
  36.                 pNbr.SqlDbType = SqlDbType.Float;
  37.                 pNbr.ParameterName = "@nbrval";
  38.                 pNbr.Precision = 18;
  39.                 pNbr.Scale = 9;
  40.                 cmd.Parameters.Add(pNbr);
  41.                 nbTotal = 0;
  42.                 startTime = DateTime.Now;
  43.                 Console.Write("Lancement du test avec des requêtes paramétrées..." );
  44.                 for (int i = 0, length = rnd.Length; i < length; i++)
  45.                 {
  46.                     pNbr.Value = rnd[i];
  47.                     nbTotal += (int)cmd.ExecuteScalar();
  48.                 }
  49.                 Console.WriteLine(" {0} millisecondes - {1} lignes traîtées", ((TimeSpan)(DateTime.Now - startTime)).TotalMilliseconds, nbTotal);
  50.                 cmd.Parameters.Clear();
  51.                 nbTotal = 0;
  52.                 startTime = DateTime.Now;
  53.                 Console.Write("Lancement du test avec des requêtes classiques..." );
  54.                 for (int i = 0, length = rnd.Length; i < length; i++)
  55.                 {
  56.                     cmd.CommandText = string.Format("select count(id) from test where strval like substring('{0}', 1, 4) + '%' or nbrval between {0} - .01 and {0} + .01", rnd[i].ToString().Replace(',', '.'));
  57.                     nbTotal += (int)cmd.ExecuteScalar();
  58.                 }
  59.                 Console.WriteLine(" {0} millisecondes - {1} lignes traîtées", ((TimeSpan)(DateTime.Now - startTime)).TotalMilliseconds, nbTotal);
  60.                 startTime = DateTime.Now;
  61.                 Console.Write("Fermeture de la connexion et destruction des objets..." );
  62.                 cmd.Dispose();
  63.                 cnx.Close();
  64.                 cnx.Dispose();
  65.                 Console.WriteLine(" {0} millisecondes", ((TimeSpan)(DateTime.Now - startTime)).TotalMilliseconds, nbTotal);
  66.             }
  67.             Console.WriteLine("Fin du bench" );
  68.             Console.ReadKey(true);
  69.         }
  70.     }
  71. }


 
Résultat :
 


Début du bench
Passe 1 commence à 23:32
Génération du jeu de test (1000 valeurs à rechercher)... 0 millisecondes
Etablissement de la connexion... 62,5 millisecondes
Lancement du test avec des requêtes paramétrées... 1828,125 millisecondes - 148913 lignes traîtées
Lancement du test avec des requêtes classiques... 5890,625 millisecondes - 148913 lignes traîtées
Fermeture de la connexion et destruction des objets... 0 millisecondes
Passe 2 commence à 23:32
Génération du jeu de test (1000 valeurs à rechercher)... 0 millisecondes
Etablissement de la connexion... 0 millisecondes
Lancement du test avec des requêtes paramétrées... 2046,875 millisecondes - 148824 lignes traîtées
Lancement du test avec des requêtes classiques... 6078,125 millisecondes - 148824 lignes traîtées
Fermeture de la connexion et destruction des objets... 0 millisecondes
Passe 3 commence à 23:32
Génération du jeu de test (1000 valeurs à rechercher)... 0 millisecondes
Etablissement de la connexion... 0 millisecondes
Lancement du test avec des requêtes paramétrées... 2437,5 millisecondes - 149034 lignes traîtées
Lancement du test avec des requêtes classiques... 6578,125 millisecondes - 149034 lignes traîtées
Fermeture de la connexion et destruction des objets... 0 millisecondes
Passe 4 commence à 23:32
Génération du jeu de test (1000 valeurs à rechercher)... 0 millisecondes
Etablissement de la connexion... 0 millisecondes
Lancement du test avec des requêtes paramétrées... 1812,5 millisecondes - 149174 lignes traîtées
Lancement du test avec des requêtes classiques... 5765,625 millisecondes - 149174 lignes traîtées
Fermeture de la connexion et destruction des objets... 0 millisecondes
Passe 5 commence à 23:33
Génération du jeu de test (1000 valeurs à rechercher)... 0 millisecondes
Etablissement de la connexion... 0 millisecondes
Lancement du test avec des requêtes paramétrées... 1890,625 millisecondes - 147980 lignes traîtées
Lancement du test avec des requêtes classiques... 5859,375 millisecondes - 147980 lignes traîtées
Fermeture de la connexion et destruction des objets... 0 millisecondes
Fin du bench


 
On peut sans problème s'attendre à des gains similaires avec n'importe quel autre langage/SGBD, puisqu'il ne s'agit pas ici d'exploiter des spécificités particulières à .NET ou SQL Server, mais bel et bien les avantages connus de ce système de passage de valeurs aux requêtes.


Message édité par MagicBuzz le 17-02-2007 à 23:39:00
Reply

Marsh Posté le 17-02-2007 à 23:59:23    

Ah ouais, 3 fois plus rapide quand même... Pensais po que ça montait autant :ouch:
 
 
PS : par contre c'est quoi ce code pourri ? :whistle:

Reply

Marsh Posté le 18-02-2007 à 07:41:59    

Il a quoi mon code ? :o
 
Sinon, pour le coup des 3x plus rapide, il faut prendre avec des pincettes. Ici, les requêtes sont simples et la base petite. Donc le temps du calcul du plan d'exécution est considérable par rapport au temps d'exécution de la requête. Idem, je ne récupère aucun volume à chaque fois, puisque je fais un COUNT. Dans une application "normale", évidement, que ce soit des requêtes paramétrées ou non, le temps de recherche des données, puis le rappatriement du résultat entre le SGBD et le serveur d'appli est évidement fixe, et plus important que dans mon test. Le gain sera donc moins flagrant. Par contre, comme le montre cet exemple, il existe bel et bien.

Reply

Marsh Posté le 18-02-2007 à 09:55:42    

petite précision par rapport a tout ca.
 
Outre le gain sur le calcul du plan d'exécution c'est surtout le compilation de la requete qui ne sera plus a refaire du coté sgbd, car si je me souviens bien un des premiers tests du sgbd est de hascoder la requete et de voir si il n'a pas déja une version égale en cache  
et donc l'interet de: "select * from table where champ= :1" est que la requete ne varie pas suivant le critère de recherche.
 
par rapport a :
"select * from table where champ= 'toto'"
"select * from table where champ= ' toto'"
"select * from table where champ= 'Toto'"
"select * from table where champ= 'TOTO'"
 
Pour le recalcul du plan d'exécution la a mon avis c'est moins clair vu que les statistiques peuvent jouer.

Reply

Marsh Posté le 18-02-2007 à 12:08:40    

lkolrn a écrit :

Mais c'est associé à php que depuis la v5 il me semble, et MySql a mis longtemps à se mettre à jour sur cet aspect là aussi...


 
Faut pas oublier qu'il n'y a pas que mysql dans la vie...:o


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 18-02-2007 à 12:09:34    

MagicBuzz a écrit :

Il a quoi mon code ? :o
 
Sinon, pour le coup des 3x plus rapide, il faut prendre avec des pincettes. Ici, les requêtes sont simples et la base petite. Donc le temps du calcul du plan d'exécution est considérable par rapport au temps d'exécution de la requête. Idem, je ne récupère aucun volume à chaque fois, puisque je fais un COUNT. Dans une application "normale", évidement, que ce soit des requêtes paramétrées ou non, le temps de recherche des données, puis le rappatriement du résultat entre le SGBD et le serveur d'appli est évidement fixe, et plus important que dans mon test. Le gain sera donc moins flagrant. Par contre, comme le montre cet exemple, il existe bel et bien.


 
Sur un exemple standard ca donne quoi ?

Reply

Marsh Posté le 18-02-2007 à 13:06:07    

casimimir a écrit :

Pour le recalcul du plan d'exécution la a mon avis c'est moins clair vu que les statistiques peuvent jouer.


ouais, abus de langage : quand je parle de calcul du plan, ça inclu la compilation de la requête ;)

Reply

Marsh Posté le 18-02-2007 à 13:07:04    

nycius a écrit :

Sur un exemple standard ca donne quoi ?


ben j'ai pas d'exemple d'application pourrave écrite sans requêtes paramétrée. donc j'ai pas de cas réel à te proposer pour effectuer une comparaison :spamafote:

Reply

Marsh Posté le 18-02-2007 à 13:46:31    

par rapport a une requete classique ? ou meme une class

Reply

Marsh Posté le 18-02-2007 à 18:58:15    

un like et un between, c'est tout ce qu'il y a de plus classique :spamafote:
 
je vois pas ce que vous leur voulez à mes requêtes :o

Reply

Marsh Posté le 18-02-2007 à 21:21:39    

skeye a écrit :

Faut pas oublier qu'il n'y a pas que mysql dans la vie...:o

Sauf quand on rejoint une équipe de dev. qui pensait que si, justement :o
 
'ai bien fait de partir moa...

Reply

Marsh Posté le 19-02-2007 à 08:07:20    

Attention aussi aux clauses LIKE. Imaginez vous avec dans votre appli un truc où l'utilisateur saisie le début d'une string, et ça renvoie tout les strings correspondantes. Si le mec veut inclure un % ou un _, ça foire évidemment (ce sont des wildcards). Là, mysql_real_escape_string ne servira strictement à rien... Il faut en plus échapper donc % et _, du style :
 

Code :
  1. $start = addcslashes(mysql_real_escape_string($_GET['start']), '%_')) . '%';


 
Si le mec tape %_'pouet', on aura bien :

Code :
  1. \%\_\'pouet\'%

Reply

Marsh Posté le 19-02-2007 à 08:11:15    

mon dieu que c'est laid les \ pour échapper du sql.

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed