« retourner à la page principale du blog

Prévenir les injections SQL avec PHP et MySQL

Tout d’abord, pour ceux qui ne savent pas ce qu’est une attaque SQL, je vous fais une brève explication.

Exemple d’attaque par injection SQL

Une personne mal intentionnée peut tenter d’abuser votre code PHP en insérant des morceaux de SQL à des endroits que vous vous n’y attendez pas sur votre site web. Par exemple, une page qui montre les produits d’une certaine catégorie à l’aide d’un paramètre GET. Une version simplifiée de votre code pourrait ressembler à ceci:

<?php
// connection à la base de donnée
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$query = 'SELECT * FROM products WHERE category="'
        .$_GET['category'].'"';
$mysqli->query($query);
// ... suite du code ...
?>

Avec $_GET['category']='rasoirs', la requête ressemblerait à ceci:

SELECT * FROM products WHERE category="rasoirs"

Si l’utilisateur réussi à passer $_GET['category']='rasoirs" OR 1=1', la requête se fait transformer en quelque chose du genre:

SELECT * FROM products WHERE category="rasoirs" OR 1=1

L’exemple ici n’est pas si dangereux, mais on peut facilement imaginer des requêtes du genre:

UPDATE products SET en_special="1" WHERE category="rasoirs"

devenir des requêtes du genre:

UPDATE products SET en_special="1" WHERE category="rasoirs" OR 1=1

Regardez cet exemple concret d’une attaque par injection SQL ;) .

Comment on faisait pour éviter ce problème

Une pratique encore valide aujourd’hui est de ne jamais mettre directement des données entrées par un utilisateurs à des endroits qui pourraient affecter de manière nuisible le programme. Dans l’exemple précédent des catégories, on aurait pu faire quelque chose qui ressemble à:

switch($_GET['category']){
  case 'rasoirs':
    $query = 'SELECT * FROM products WHERE category="rasoirs"';
    break;
// etc.
}

Bien sûr, on doit souvent utiliser les entrées de l’utilisateurs pour fabriquer des requêtes. Il faut alors filtrer ce que l’utilisateur envoie, avec des fonctions du genre mysql_real_escape(). Mais cette fonction peut maintenant être compté comme désuette grâce à la compilation des requêtes.

Nouvelle façon

Grâce aux nouveaux (okay, pas si nouveau) compiled statements (requêtes compilées), on peut désormais être certain que l’utilisateur ne réussira pas à déborder hors de ce qu’on avait prévu initiallement. Voici un exemple:

$query = 'INSERT INTO users (name, password, date_added) '
        .'VALUES (?, ?, ?);
$stmt = $mysqli->prepare($query);
if ($stmt){
  $stmt->bind_param('ssi', $name, $password, $date_added);
  // s pour string, i pour integer
  $stmt->execute();
}

La requête SQL est donc compilée avant même de savoir ce qui se tiendra à la place des ?. On ne fait qu’assigner plus tard ce qui les remplacera.

Premier avantage, qui est le sujet de mon article, est que ça prévient les attaques SQL. Dans l’exemple des catégories de produits ci-haut, même si l’utilisateur réussissait à assigner une valeur du genre rasoirs" OR 1=1 à notre variable qui ira dans notre requête SQL, la requête était déjà compilée. La requête aura donc l’effet de celle-ci:

SELECT * FROM products WHERE category="rasoirs\" OR 1=1"

Elle cherchera donc RÉELLEMENT les produits appartenant à la catégorie rasoirs" OR 1=1 (qui n’existe pas).

Deuxième avantage des requêtes précompilées, c’est que si vous avez à exécuter plusieurs requetes dont seulement certain paramètres changent, en ne compilant qu’une seule fois la requête et en l’exécutant avec différentes variables, vous sauver du temps-machine. Lorsque vous y allez « à l’ancienne », le requête est compilée à chaque fois.

Conclusion et liens

La plupart des bases de données offrent aujourd’hui la possibilité de compiler ses requête à l’avance et je trouve que c’est une excellente pratique de profiter de cette fonctionnalité.

Les PDO (PHP Data Objects) n’offrent qu’une abstraction très mince au-dessus des bases de données. Ils n’offrent générallement pas de fonctionnalités ajoutées. Pourtant, les concepteurs ont décidé d’implémenter dans les PDO la possibilité de compiler ses requêtes même lorsque la base de donnée utilisée ne l’accepte pas! Ils s’en chargent pour nous. Pour vous dire à quel point c’est important.

Si vous voulez un peu plus d’information sur les attaques SQL, vous pouvez lire cette page de Wikipedia et cette page web.

Un commentaire

  1. Bon code, Bad code – Hooba Studios dit :
    16 juillet 2009 à 22:19

    [...] En insérant directement des valeurs fournies par l’utilisateur (ici, dans la barre d’adresse) sans les avoir filtrés au préalable est une mauvaise pratique car elle ouvre très grand les portes aux attaques par injection SQL. [...]

Laisser un commentaire

« retourner à la page principale du blog