Site Perso de

Thomas JANNAUD

Des actualités personnelles sous un style impersonnel, et inversement.



PHP/MySQL conseils/tips
In French and English 11 Septembre 2011

First part in French, 2nd part in English

L'une des choses les plus embêtantes à gérer dans un site est l'échappement de guillemets pour éviter les injections MySQL. Et même sur un site qui tourne depuis 3 ans il y a toujours à régler des petits bugs.

Les problèmes peuvent venir de plusieurs endroits :

Injections SQL

Quand on voit ça, on panique un peu et on va tout échapper une fois, deux fois, trois fois. Et après on voit que quand on affiche ça en html on obtient des "Je m\\\\'appelle Thomas", alors on va faire stripslashes 2 ou 3 fois avant d'afficher les commentaires en html. D'une part ça ne résoudra pas le problème et d'autre part des entrées dans la base de données auront été insérées avec l'ancien code pour échapper les chaînes, d'autres avec votre nouveau code. Bref vous allez vite avoir un truc tout dégoûtant.

La bonne manière de faire est :

  1. Tout doit être nickel dans la BDD et exactement comme l'utilisateur l'a écrit, même s'il a mis des pourritures, des balises scripts, des injections sql, ... Il ne faut pas stocker des \' ou autres, il faut stocker qqchose de non transformé. Par contre il faut faire attention à ne pas exécuter ses injections.
  2. Dans ce but, il faut trouver un moyen d'encoder l'information sans la corrompre et sans l'exécuter. Le moyen standard est d'utiliser mysql_real_escape_string() ou pdo->quote() si l'on utilise PDO.
  3. Enfin, il faut échapper tout ça quand on le sort de la BDD pour l'afficher, avec htmlspecialchars(). C'est ce qui fait que les choses comme <script> deviendront &lt;script&gt;

En bref :

if (get_magic_quotes_gpc()) {
$_POST['message'] = stripslashes($_POST['message']);
...
}
...
$message = mysql_real_escape_string($_POST['message']);
$query = "INSERT INTO comments(message, ...) VALUES('$message',...)";
...

Si vous utilisez PDO
$message = mypdo->quote($_POST['message']);
et il faudra alors écrire VALUES($message,...) au lieu de VALUES('$message',...) car contrarement à mysql_real_escape_string, quote va ajouter des guillemets devant et derrière le texte au contraire de mysql_real_escape_string.

ON UPDATE TIMESTAMP...

En voulant corriger certains problèmes et on a décidé de refaire les entrées dans la BDD pour remplacer tous les \' par des '.
UPDATE comments SET message=REPLACE(message, "\'", "'")

Problème : si on le fait les dates des enregistrements concernés seront changé à aujourd'hui si elles ont l'attribut ON UPDATE CURRENT_TIMESTAMP.

Un moyen de contourner le problème :
UPDATE comments SET date=date, message=REPLACE(message, "\'", "'")

Encodage

Dernière chose pour conclure : n'oubliez pas de toujours utiliser utf-8 partout. Ça vous permettra d'écrire é au lieu de &ecute; ainsi que des choses comme 馬鹿. Utilisez utf8_bin plutôt que les autres options proposées, l'ordre lexicographique sera l'ordre naturel des entiers/octets et ça sera beaucoup plus pratique si vous interfacez MySQL avec d'autres langages.


One of the annoying things about writing a website in PHP/MySQL is to escape every input string to avoid sql injections. This website has been up and running for more than 3 years and yet another little bug has just been fixed. Again.

Problems can arise in various places :

SQL injections

When you see that, you are super afraid not to escape quotes enough that you panic, escape strings twice, and soon you will have some "I\\\'m Joe" on your html. So you will strip them twice when printing the comments in html. And so on. And some entries in your database will be inserted with some first version escaping code and some others with the second version. And you will have some bugs and things will get messy and dirty.

The right way to do it is :

  1. User messages in the DB must be uncorrupted, exactly what they entered, even if they entered some evil shit. Don't store them with \', ... but store them without executing them.
  2. Because of that you need to escape strings in the correct way before pushing them to the DB so that you store perfect data. Use mysql_real_escape_string() or pdo->quote() if using PDO.
  3. html_escape them with htmlspecialchars() when printing them on html (so that things like <,script> become &lt;script&gt;)

In brief :

if (get_magic_quotes_gpc()) {
$_POST['message'] = stripslashes($_POST['message']);
...
}
...
$message = mysql_real_escape_string($_POST['message']);
$query = "INSERT INTO comments(message, ...) VALUES('$message',...)";
...

Note that if you use a PDO object, do
$message = mypdo->quote($_POST['message']);
and in the query line do VALUES($message,...) instead of VALUES('$message',...) because there are already some quotes before and after the text, contrary to mysql_real_escape_string.

ON UPDATE TIMESTAMP...

After fixing the slash problems in the code, we fixed them in the database, by doing something like :
UPDATE comments SET message=REPLACE(message, "\'", "'")

The problem is that by doing so, all the timestamps of the changed records will be set to today's date if the date in your table structure have their default attribute set to ON UPDATE CURRENT_TIMESTAMP. You won't be able to revert this if you do not have backed up your database.

One way to avoid that is to do : UPDATE comments SET date=date, message=REPLACE(message, "\'", "'")

Encoding

Among other tips, don't forget to set all your encodings to utf-8. This will enable non English people to write letters like é, ç or 馬鹿. Use utf8_bin among all other options as it will define the lexicographic order to be the same as of the integers/bytes which is very useful when interfacing MySQL with other programming languages.

À lire aussi :
Laissez un commentaire !

Pas besoin de vous connecter, commencez à taper votre nom et une case "invité" apparaîtra.