PHP et Injection SQL
Par Francis Besset le mardi, 22 janvier 2008, 20:19 - PHP / SQL - Lien permanent
| Partager |
Lorsque l'on développe en PHP avec l'utilisation d'un SGBD, la hantise est de laisser la possibilité d'une injection SQL.
Cet article va décrire les différentes étapes afin d'éviter toute injection SQL.
Tout d'abord pour l'enregistrement des données dans une base de données, il faut utiliser mysql_real_escape_string(). Cette fonction va ajouter des antislashes sur les guillemets simples, les guillemets doubles et d'autres caractères. Pour plus d'information sur cette fonction, je vous conseille de voir la documentation officielle.
Me diriez-vous : Magic Quotes ne sert-il pas à ça ?
Tout d'abord il faut savoir que Magic Quotes est amené à disparaitre avec l'arrivé de PHP6. De plus par défaut, Magic Quotes est désactivé avec PHP5.
Voici comment désactiver les Magic Quotes :
Via le fichier de configuration php.ini :
magic_quotes_gpc = Off
Via .htaccess :
SetEnv MAGIC_QUOTES 0
Ou :
php_flag magic_quotes_gpc 0
Si ces manipulations n'ont pas fonctionnées et que vous n'avez pas accès au fichier de configuration de PHP, rien est perdu.
Il va vous suffir de mettre le code ci-dessous au début de vos scripts :
<?php
define('MAGIC_QUOTES', get_magic_quotes_gpc());
// Création de la fonction
function no_magic_quotes(&$array) {
/*
Script réalisé par Besset Francis
Vous pouvez utiliser et distribuer librement ce code
à condition de laisser le nom du réalisateur du code.
http://www.apercite.fr
*/
// Détection si magic_quotes est activé
if(MAGIC_QUOTES) {
foreach($array as $key => $val) {
// Si la variable est un tableau
if(is_array($val)) {
// Alors on rappelle la fonction pour traiter ce tableau
no_magic_quotes($array[$key]);
}
// Sinon si la variable n'est pas un numérique alors elle est susceptible de contenir des antislahs ou quotes ajouté par Magic Quotes (Sybase)
elseif(!is_numeric($val)) {
// Alors on lui enlève les antislashs ajoutés par Magic Quotes
$array[$key] = stripslashes($val);
}
}
}
}
// Voici la liste des superglobales touchées par Magic Quotes :
no_magic_quotes($_GET);
no_magic_quotes($_POST);
no_magic_quotes($_COOKIE);
?>
A noter que le code ci-dessus, enlèvera les antislashes ajouter par Magic Quotes uniquement si Magic Quotes est activé.
Nous voilà donc séparé des Magic Quotes et nous tendons les bras grand ouvert vers mysql_real_escape_string().
Pour l'affichage d'une donnée sur une page venant de la base de données ou des $_POST, $_COOKIE, $_GET, je vous conseille grandement d'utiliser htmlspecialchars() avec l'option ENT_QUOTES afin de transformer les caractères suivant en entités HTML :
- " & " devient " & "
- " " " devient " " "
- " ' " devient " #039; "
- " < " devient " < "
- " > " devient " > "
Ainsi on ne pourra pas "casser" votre joli design lors de l'affichage de données dynamiques.
Je vous conseil de visiter la documentation de cette fonction afin de pouvoir voir les différentes options qu'elle peut prendre en compte.
Pour bien être clair, que ce soit sur la page en elle même ou bien lors de l'affichage de données dans un formulaire, il faut appliquer afin d'éviter tout "cassage" de la page.
Mais c'est bien joli tout ça mais lorsque l'on renvoi le formulaire avec des entités HTML qui on été encodé par htmlspecialchars(), ça renvoit les entités et pas les caractères correspondant et donc l'enregistrement de ces données dans le SGBD ça va pas être du joli.
Heureusement j'ai LA solution ! Il va falloir décoder les entités HTML en caractères normaux. Pour celà on va utiliser le contraire de htmlspecialchars() : htmlspecialchars_decode(). Malheureusement cette fonction est accessible qu'à partir de PHP 5.1.0. Mais pour vous j'ai développé le sosi de cette fonction pour PHP 4 :
<?php
// Si la fonction htmlspecialchars_decode() n'existe pas
if(!function_exists('htmlspecialchars_decode')) {
// Alors on la crée
function htmlspecialchars_decode($string, $quote_style=ENT_COMPAT) {
/*
Script réalisé par Besset Francis
Vous pouvez utiliser et distribuer librement ce code
à condition de laisser le nom du réalisateur du code.
http://www.apercite.fr
*/
$search = array('&', '<', '>');
$replace = array('&', '<', '>');
if($quote_style >= ENT_COMPAT) {
$search[] = '"';
$replace[] = '"';
if($quote_style == ENT_QUOTES) {
$search[] = ''';
$replace[] = "'";
}
}
return str_replace($search, $replace, $string);
}
}
?>
La fonction ci-dessus a exactement le même comportement que la fonction officielle portant ce même nom.
Si jamais vous utilisez PostGreSQL, il va vous falloir protèger vos données nom pas avec mysql_real_escape_string() mais pg_escape_string() pour un champ texte et pg_escape_bytea() pour un champ de type bytea.
Attention : Bien sûr il est inutile d'utiliser mysql_real_escape_string(), pg_escape_string() et pg_escape_bytea() sur des champs de type numérique. Il est donc bien évident qu'aucun caractère ne sera précédé d'un antislash puisque la valeur sera logiquement une valeur numérique.
Cependant assurez-vous bien au préalable que la valeur est bien un numérique avec is_numeric().



Commentaires
J'ajoute mon grain de sel au billet. Pour éviter les injections SQL, il faut :
- Contrôler toutes les données en provenance de l'utilisateur
- Utiliser des requêtes préparées ou procédures stockées via les interfaces MySQLI ou PDO
Le contrôle peut être accentué de différentes manières :
- Contrôle des types
- Filtrage par liste blanche (liste de valeurs autorisées)
- Filtrage par liste noire (liste de valeurs exclues)
- Filtrage par liste grise (filtrage des deux types de listes)
- Contrôle des formats (email, numéro de sécu, code postal...)
- ...
Comment bien filtrer ?
- Avec les fonctions ctype_*()
- Avec l'extension filter de PHP5
- Avec des bons masques de regex
- ...
Je terminerai par dénoter une information primordiale et absente dans ton billet, à savoir : pourquoi les magic_quotes doivent être désactivées ?
1/ Parcequ'elles coûtent du temps de traitement non négligeable : addlsashes() sur chaque données des tableaux GPC, puis stripslashes() sur chaque valeur à échapper.
2/ Parceque les magic_quotes font appel à addslashes() qui n'est pas suffisant pour protéger efficacement les injections. Donc l'échappement avec addlashes() peut coûter du temps pour rien.
++
Néanmoins ça reste une petite parade non négligeable quand on débute.
Hugo.
Merci pour l'article et le premier commentaire, je les trouve tous deux excellents !
Je ne comprend l'utilité d'utilisé ENT QUOTES dans la fonctions htmlspecialchars puisque mysql_real_escape_string échappe les ' et " entre autre.
Merci
Si j'utilise htmlspecialchars() avec ENT_QUOTES c'est pour qu'il me transforme ' et " en entité HTML.
Cependant je n'utilise pas htmlspecialchars() avant une requête SQL mais plutôt avant l'affichage des données sur une page. Ainsi tu n'as aucun risque que tes formulaires soit cassés.
J'utilise htmlspecialchars_decode() lorsque je reçois les données avec ENT_QUOTES afin de faire l'inverse de htmlspecialchars() avec ENT_QUOTES. Ainsi ce que j'avais encodé en entité HTML à l'affichage redevient des apostrophes et des guillemets normaux. Et c'est donc pour celà que j'utilise mysql_real_escape_string().
Ok Je comprend.
Donc quand tu enregistres tes ' et des " dans la bdd ils seront en entité html.
Ce qui est pareil pour les caractères du genres <b>...
Mais encore une question. Moi je n'utilise jamais htmlspecialchars_decode.
Car Par exemple dans ma bdd le texte est sous forme d'entité html (transformation des < et > )mais il s'affiche correctement sur la page web.
Esce-normal?
Visiblement tu n'as pas saisie la chose.
Quand j'enregistre les données dans la base de données, les informations ne sont pas en entités HTML. Elle sont enregistré comme tu les vois là : " et '
C'est seulement lorsque j'affiche les données sur la page que je les transforme en entités HTML.
Si tu les vois normalement sur ta page malgré qu'elles soient en entités HTML c'est parce que c'est le navigateur qui se charge de la transformation du texte qu'il reçoit en affichage graphique qu'il te fait. Donc ne t'inquiète pas c'est normal.
Je te cite
Quand j'enregistre les données dans la base de données, les informations ne sont pas en entités HTML. Elle sont enregistré comme tu les vois là : " et '
Je pense que cela est faux. Car j'ai testé moi même . J'ai écris "'<>'éè.
Et après je vais voir dans la bdd. Et je trouve des choses comme cela " .
Donc j'en déduis que la fonction htmlspecialcahrs transforme les caractères spéciaux voir dangereux en entité html.
Es-tu d'accord?
La fonction htmlspecialchars() avec l'option ENT_QUOTES transforme bien les caractères : &, ", ', <, >
en entité HTML on est bien d'accord. Cependant j'utilise UNIQUEMENT htmlspecialchars() avec l'option ENT_QUOTES à l'AFFICHAGE des données sur la page.
Et j'utilise htmlspecialchars_decode() avec l'option ENT_QUOTES lorsque que je RECOIS des données de formulaire par exemple AVANT de les ENREGISTRER dans la BDD. Donc htmlspecialchars_decode() fait tout le contraire de ce que fait htmlspecialchars(). C'est à dire qu'au lieu de transformer les caractères en entités HTML, ça transformera les entités HTML en caractères normaux.
Cependant j'utilise UNIQUEMENT htmlspecialchars() avec l'option ENT_QUOTES à l'AFFICHAGE des données sur la page.(C'est toi^^)
Donc si tu utilises cela si par exemple ici j'écris > < & " ' , je devrais voir apparatraitre des entité html? (car c'est l'affichage)
Mais ce n'est pas le cas?
Pour que je comprenne (même si je crois que tu ma déjà expliqué .je suis borné)
Dis moi stp , comment vas tu traiter ce message de l'enregistrement à l'affichage.
Moi je ferais pour l'enregistrement
mysql_real_escape_string(htmlspecialchars($message));
et à l'affichage
nl2br($message);
Merci de m'expliquer tu es très sympathique.
PS : c'est bon j'ai enfin compris^^
En faite c'est dans le code source de la page que le texte change .
Mais le navigateur affiche toujours la m^me chose comme tu m'avais dis ici
c'est le navigateur qui se charge de la transformation du texte qu'il reçoit en affichage graphique qu'il te fait. Donc ne t'inquiète pas c'est normal.
Merci^
Hello,
Quelques choses à redire sur ce script.
Déjà, le titre est plus que mensonger. Tu parles bien plus des magic_quotes que des injections SQL, mais ça à la limite, on s'en fout.
Tu conseilles de mettre un ini_set('magic_quotes_gpc', 0) en début de script, sauf que ça n'aura aucun effet ! Les GPC sont récupérées et passer à addlslashes() avant même que le script ne débute réellement, et cet ini_set() n'appellera pas stripslashes() par magie.
Dans ta fonction no_magic_quotes, pourquoi faire le if(MAGIC_QUOTE) dans le foreach ? C'est bête d'évaluer plusieurs fois un truc qu'on n'évaluer qu'une fois.
Dans cette même fonction, on se demande à quoi sert ton elseif(is_string).. Toutes les variables GPC sont des chaînes de caractères, donc is_string() retournera toujours true et donc stripslashes() sera appliqué tout le temps. C'est plutôt un !is_numeric() dont tu as besoin.
On peut aussi se demander ce qu'il se passera si l'option magic_quotes_sybase est activée (pas grand chose en fait, ta fonction n'effacera pas l'effet des magic_quotes).
Le plus drôle vient ensuite ! D'où crois-tu que GPC vienne ? GPC est l'acronyme de GET, POST, COOKIE. Alors, pourquoi appliquer ta fonction sur REQUEST, FILES et SERVER ? Ca n'a aucun intérêt puisqu'ils ne sont pas affecté par les magic_quotes.
htmlspecialchars() n'a pas pour vocation d'"empêcher de casser notre présentation". Le but premier d'htmlspecialchars(), c'est d'échapper une chaîne destinée à être mise dans du html. Après, cet échappement à pour conséquence d'empêcher de casser notre design, sauf qu'une conséquence n'est pas un but !
C'est bien de souligner qu'il ne faut pas utiliser *_escape_string sur des nombres, mais pourquoi se restreindre à des nombres ?
Si on ne demande que des caractères alphabétiques, pas besoin de *_espace_string non plus, ctype_alpha suffit.
Si on ne demande un format précis, pas forcément besoin de *_escape_string : ça dépend du format.
En conclusion, l'idée est bonne et le message principal est là ("faites attetion !"), mais la réalisation est un peu trop superficielle et manque de rigueur.
Rose,
Tout d'abord merci pour ta critique.
En effet tu as raison pour ini_set('magic_quotes_gpc', 0). Celà ne changera rien au comportement des Magic Quotes.
Pour la condition de if(MAGIC_QUOTES), j'avais vu l'optimisation cependant je n'ai pas encore eu le temps de corriger ça.
Quand à ta remarque sur ma condition elseif(is_string) qui ne sert à rien je suis bien d'accord ! J'ai dû tout simplement me mélanger les pinceaux lors de la création de la fonction. A la place j'ai mis !is_numeric().Désormais c'est fait.
Si magic_quotes_sybase est activé alors la fonction stripslashes() supprimera uniquement le second guillemet simple ajouté par magic_quotes_sybase. Je t'invite à lire la doc sur la fonction stripslashes() : http://www.php.net/stripslashes
Pour ta remarque sur GPC = Get, Post, Cookie, celle-ci est très bonne. Je corrige ça de suite.
A propos de htmlspecialchars(), je t'appelerais Maître Capello pour les fois prochaines.
Pour les fonctions ctype_alpha, c'est une bonne remarque, encore faut-il que lors de l'installation/compilation de php on n'ait pas désactivé cette option.
Pour finir, désolé du temps de validation de ton commentaire.
Pourquoi pas un
$valeur = htmlentities($_POST['valeur'], ENT_QUOTES);
?
Je n'utilise pas htmlentities() car cette fonction va nous convertir les caractères accentués et bien d'autres en entités HTML.
Par exemple le "é" va devenir "é". Hors je n'ai encore jamais vu un caractère "é" réussir à faire une injection de code HTML.
Ce que l'on recherche donc avec htmlspecialchars() c'est convertir en entités HTML seulement les caractères susceptibles de nous faire une injection HTML et qui peut donc amener à une faille XSS.