Général

Les PCRE

Les POSIX

Pratique

Linux

Spécial php

Les billets de fred

La magie noire des apostrophes magiques.

PHP dispose d'une fonctionnalité particulière : les apostrophes magiques (magic quotes). Mais est-ce vraiment une bonne magie ? Pour les habitués, c'est de la magie noire et pour les débutants, c'est de la sorcellerie !

Comment définit-on une chaîne de caractères ?

En PHP, on commence et on finit une chaine de caractères par une apostrophe.

Ainsi :
<?php
// la chaîne commence ici . .
//        |         . . . . .
//        |         . .  et se termine là
//        |                       |
//        v                       v
$chaîne = 'la chaîne de caractères';
?>

Remarque : Cela est valable également avec les guillemets, mais je vous laisse lire l'article :
"Les chaînes de caractères : soyons cohérents !".

Mais que se passe-t-il si la chaîne contient justement une apostrophe ?
<?php
// la chaîne commence ici . .
//        | . . . . . . . . .
//        | . . et se termine là
//        |      |
//        v      v
$chaîne = 'Ceci n'est pas une bonne chaîne';
?>

Ce code a pour conséquence de générer une erreur de syntaxe du type "unexpected T_STRING". En effet, PHP considère le "est" placé juste après la deuxième apostrophe, comme ne faisant pas partie de la chaîne. De plus, ce mot n'étant pas précédé d'un opérateur ou d'un point-virgule, il en résulte une erreur d'analyse.

Pour éviter cette confusion, il faut donc indiquer à PHP de ne pas prendre en compte cette seconde apostrophe. On appelle ça ''échapper'' un caractère et cela consiste (en PHP) à utiliser un antislash (caractère "\" : AltGr + 8 de votre clavier) de la manière suivante :

<?php
// la chaîne commence ici . .
//        |                 .
//        |                 . . . et se termine là
//        |                                   |
//        v                                   v
$chaîne = 'Ceci n\'est pas une mauvaise chaîne';
?>

PHP ne prend donc pas en considération l'apostrophe lorsqu'elle est précédée d'un antislash.
Mais que se passe-t-il si nous voulons justement utiliser un antislash dans notre texte ?
Il faut donc l'échapper lui aussi, ce qui donne :
<?php
// la chaîne commence ici . . . .

//        |                     .
//        |                     . .  . et se termine là
//        |                                         |
//        |          Deux antislashs échappés ici   |
//        |                       |        |        |
//        v                       v        v        v
$chaîne = 'Le fichier est dans c:\\windows\\system32';
?>

Remarque : Si vous utilisez des guillemets, il n'est pas utile d'échapper les apostrophes, pas contre vous devez échapper les guillemets.
Ex: $chaîne = "Il me dit : \"C'est une chaîne !\".";

L'échappement des chaînes de caractères n'est pas spécifique à PHP, et un grand nombre de langages utilisent la technique de l'antislash.

Par ailleurs, si on aborde le langage SQL, on constate que l'échappement des apostrophes n'est pas réalisé avec des antislashs, mais en doublant celles-ci. Ainsi, on peut écrire la requête SQL suivante :

           L'apostrophe est ici . . .
                                    .
                                    v
INSERT INTO boites (nom) VALUES ('L''espace').

Seulement, c'est sans compter sur les petites spécificités de MySQL qui autorise également l'échappement par antislashs.
Cette caractéristique de MySQL a une conséquence, si nous écrivons ceci :
<?php
$donnée = 'L\'espace';
$sql = "INSERT INTO boites (nom) VALUES ('$donnée')";
?>

Dans la variable $donnée, nous observons le contenu suivant :
L'espace
L'antislash n'est pas resté puisqu'il était là uniquement pour indiquer à PHP de ne pas considérer la deuxième apostrophe comme la fin de la chaîne.

Maintenant, si nous considérons le contenu de la variable $sql, nous avons :

            La chaîne commence ici
                                 |
                                 | Et se termine là
                                 | |
                                 v v
INSERT INTO boites (nom) VALUES ('L'espace')

Vous comprenez bien que cela va générer une erreur pour le moteur de données.

La solution est donc d'échapper toutes les apostrophes et les antislashs contenus dans la chaîne de caractères.
Echappement des données saisies.

Bien évidemment, vous ne pouvez pas demander à vos utilisateurs de saisir leur texte en échappant les apostrophes et les antislashs. Vous allez donc le faire vous-même avant de construire vos requêtes SQL. Pour cela, il existe la fonction "addslashes" qui s'utilise de la manière suivante :

<?php
$donnée = $_POST['texte'];
$donnée = addslashes( $donnée );
$sql = "INSERT INTO boites (nom) VALUES ('$donnée')";
?>

Remarque : En y regardant de plus près, on s'aperçoit d'ailleurs que MySQL, comme la plupart des autres bases de données, propose une fonction pour échapper les caractères spéciaux: mysql_real_escape_string.
Il est donc préférable d'utiliser cette fonction qui prend en compte les spécificités de ce SGBD et par extension, il devient préférable d'utiliser les fonctions dédiées à la préparation des chaînes de caractères pour tous les autres moteurs de données (ex : pg_espace_string pour PostgreSQL).

Les apostrophes magiques.

Vous l'aurez compris, le problème d'échappement est le plus souvent lié à la saisie de données. Sur cette constatation, les développeurs de PHP ont inventé les "apostrophes magiques". Sous cette appellation ésotérique, se cache en fait un principe très simple : toutes les données contenues dans les tableaux $_GET, $_POST et $_COOKIE sont échappées avant d'être transmises au script.

Ainsi, lorsque vous exécutez ce script :

<?php
if (isset($_REQUEST['texte'])) $texte = $_REQUEST['texte'];
else $texte = '';
?>
<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="post">
<p>
    <label for="texte">Texte : </label>
    <input type="text" name="texte" id="texte" value="<?php echo $texte ?>" />
</p>
<p><input type="submit" value=" Envoyer " /></p>
</form>

Si on saisit "L'espace" dans le champ "texte", il en résultera l'affichage suivant dans la boite de saisie :
L\'espace

Remarque : Si on soumet ce même texte plusieurs fois, les antislashs seront eux aussi échappés et cela peut conduire à l'affichage suivant :
L\\\\\\\\\\\\\\\'espace

Avec ce principe, il devient donc possible d'insérer directement cette variable dans une requête à destination de MySQL.

Seulement, en dehors des requêtes pour MySQL, il y a très peu de cas où vous avez besoin d'échapper les apostrophes avec un antislash. Il y a éventuellement la génération d'un code JavaScript, mais ce n'est pas très courant.
Pourquoi avoir fait une telle chose alors ?

Normalement, cette fonctionnalité existe pour faciliter le travail du développeur et également pour éviter tout problème d'injection SQL par nos chers débutants peu attentifs. Cela fait d'ailleurs de PHP l'un des langages les moins sensibles à l'injection SQL.

C'est quoi l'injection SQL ?

J'en ai déjà parlé un peu dans mon article sur la structuration d'une application Web (Structurez vos applications "web"), mais je vais préciser.

Supposons que nous voulions créer un système d'accès par mot de passe. Pour vérifier qu'une personne s'est correctement identifiée, nous allons écrire la requête suivante :

<?php
$login = $_POST['login'];
$pass = md5( $_POST['pass'] );
$sql = "SELECT nom, email FROM membres WHERE login = '$login' AND pass = '$pass'";
?>

Si un utilisateur entre l'identifiant "charlie" et le mot de passe "monpass", nous retrouvons dans la variable $sql, le contenu suivant :

SELECT nom, email FROM membres WHERE login = 'charlie' AND pass = 'a936465247b1be65cc02997f74b5a971'

Maintenant, imaginons qu'un utilisateur crapuleux entre l'identifiant "charlie' or '" avec un mot de passe vide et que les apostrophes magiques n'existent pas.

        Le texte entré dans login commence ici . .
                                              |  .
                                              |  . . et se termine là
                                              |           |
                                              v           v                
SELECT nom, email FROM membres WHERE login = 'charlie' or '' AND pass = 'd41d8cd98f00b204e9800998ecf8427e'

On constate avec effroi que cette requête SQL retourne bien les informations associées à l'identifiant "charlie" alors que le mot de passe n'est pas correct. Pourquoi ? Tout simplement parce que la requête contient un opérateur "or" dans les conditions et comme la première est valide lorsque le champ "login" contient "charlie", la requête est satisfaite.

Donc, sans connaître un mot de passe, il est possible de s'identifier à la place de quelqu'un.

Maintenant, considérons le cas avec les apostrophes magiques, le contenu de la variable $sql sera :

        Le texte entré dans login commence ici . .
                                              |  .
                                              |  . . et se termine là
                                              |             |
                                              v             v                
SELECT nom, email FROM membres WHERE login = 'charlie\' or \'' AND pass = 'd41d8cd98f00b204e9800998ecf8427e'

Le moteur de données ne trouvera donc jamais de login contenant "charlie' or '" et l'identification n'aboutira pas.

Vous comprenez donc l'intérêt majeur des apostrophes magiques pour les débutants qui ne connaissent pas la technique d'injection SQL et écrivent du code PHP sans s'en préoccuper. Malheureusement, ces même débutants ne comprennent pas toujours bien les raisons de l'apparition de ces antislashs dans leurs affichages et ils ajoutent des stripslashes un peu partout, voire même avant la création de leurs requêtes SQL.
Activation et désactivation

La gestion de apostrophes magiques pourrait encore être simple si elles n'étaient pas désactivables. En effet, dans le fichier de configuration php.ini, il y a une option nommée "magic_quotes_gpc" ("GPC" pour Get, Post et Cookie) activée par défaut. Malheureusement, certaines personnes comme moi désactivent cette option, ce qui rend certains scripts incompatibles car ils étaient prévus pour des apostrophes magiques activées. Il faut donc imposer l'activation ou non des apostrophes magiques, ou alors développer des scripts qui s'adaptent à la configuration.
Vous pouvez par exemple utiliser ce code :
<?php
if( 1 === get_magic_quotes_gpc() ) {
    $stripslashes = create_function('$txt', 'return stripslashes($txt);');
} else {
    $stripslashes = create_function('$txt', 'return $txt;');
}

echo $stripslashes($_POST['texte']);
?>

Ainsi, que les apostrophes magiques soient activées ou non, le texte s'affichera correctement sans antislash.

Attention : N'oubliez donc pas d'échapper vos apostrophes lorsque vous construisez vos requêtes SQL.

Les apostrophes magiques depuis une source externe.

Il existe également un second système d'apostrophes magiques pour le texte en provenance de fichier ou d'une base de données. C'est-à-dire que le résultat d'une requête contenant des apostrophes sera échappé, de même lors d'une lecture dans un fichier avec file, file_get_contents, fgets, etc.
Ce comportement est également régi par une option de configuration dans le php.ini et se nomme "magic_quotes_runtime". Contrairement à "magic_quotes_gpc", cette option est désactivée par défaut mais elle peut être activée directement dans un script au moyen de la fonction set_magic_quotes_runtime. Il n'y a donc pas de problème de compatibilité si vous activez ou désactivez cette option au début de vos scripts.

Exemple:
<?php
// fichier.txt contient : "C'est le contenu"
set_magic_quotes_runtime(0);
echo file_get_contents( 'fichier.txt' ); // Affiche "C'est le contenu"
set_magic_quotes_runtime(1);
echo file_get_contents( 'fichier.txt' ); // Affiche "C\'est le contenu"
?>

Que doit-on faire alors ?

Je vous ai certainement embrouillé avec toutes ces explications et vous ne savez plus quoi penser entre activer ou désactiver les apostrophes magiques.
En fait, les apostrophes magiques ne servent à rien car, comme je l'ai expliqué, il est préférable de construire les requêtes SQL avec la fonction d'échappement dédiée au moteur de données que vous utilisez. De plus, leur activation a un impact sur les performances car PHP doit échapper les apostrophes avant de démarrer votre script et cela vous oblige à traiter vos chaînes de caractères.

Mais il faut le reconnaître, les apostrophes magiques ont sauvé la mise d'un très grand nombre de sites Web qui ne traitent pas des problèmes d'injections SQL par ignorance ou fainéantise.

Alors dans la mesure du possible, désactivez les apostrophes magiques tout en faisant extrêmement attention à la construction de vos requêtes SQL.

Enfin, si vous comptez partager vos développements n'oubliez pas de prendre en compte la possible activation des apostrophes magiques !

Par Frédéric Bouchery
ADAM Benjamin 2008 | Admin