Les injections SQL - Protection
Tester l'existant
Pour tester un champ spécifique dans une application, il suffit le plus souvent d'y insérer des caractères spéciaux du SQL, comme « ' », « " », etc. En général, si le champ est sensible à l'injection SQL, un message d'erreur va être affiché. Très souvent, ce message contient la requête qui a été exécutée, ce qui facilite le travail de l'attaquant en lui donnant des indices sur le schéma de la base.
Il est également possible de tester tout un site, soit manuellement en regardant dans le code source de l'application si les accès à la base de données sont sécurisés, soit de l'extérieur en utilisant des applications comme IBM Rational AppScan.
Exemple de rapport d'AppScan sur le site http://demo.testfire.net :
(Au passage, on peut noter qu'AppScan ne se contente pas de tester les injections SQL: il détecte également bien d'autres failles, comme les failles XSS, par exemple)
Corriger le code
Mais concrètement, comment se protège-t-on des injections SQL ?
En fait, il y a plusieurs méthodes plus ou moins contraignantes. Nous allons ici présenter ces diverses techniques en évoquant leurs avantages et inconvénients.
L'échappement de caractère
C'est la méthode la plus plébiscité sur internet; elle consiste à appliquer sur les entrées de l'utilisateur une fonction qui va échapper les caractères spéciaux du SQL. Par exemple, « ' » sera remplacé par « \' » et l'apostrophe ne sera donc pas interprété comme une fin de chaîne par le SGBD.
Par exemple, PHP met à disposition une fonction "addslashes" qui fait ce traitement. Toujours en PHP, il est possible d'activer l'option "magic_quote_gpc" dans le php.ini, ce qui aura pour effet d'appeler la fonction addslashes automatiquement sur toutes les entrées de l'utilisateur.
Malgré le fait qu'elle soit la plus connue, cette méthode n'est pas optimale:
- Le slash n'est pas toujours le caractère d'échapement des SGBD (avec Informix, par exemple, il faut doubler les caractères spéciaux pour les échapper)
- Parfois, aucun caractère spécial n'est requis pour injecter: pour les tests de nombres en SQL, on n'est pas obligé d'écrire « WHERE champ = '12345' », on peut écrire « WHERE champ = 1234 ». Si on veut injecter dans une requête utilisant la seconde forme, on voit bien qu'on n'a pas besoin d'une apostrophe de fin de chaîne: on peut directement taper du SQL. La fonction addslashes ne pourra rien pour nous dans ce cas là.
Les fonctions de vérification
Le principe est simple: on vérifie avec une fonction "maison", pour chacune des entrées de l'utilisateur, si ce qu'il a tapé correspond bien à ce qui est attendu. Par exemple, si on demande un numéro de sécurité sociale, on vérifie que notre variable contient bien 15 chiffres, et rien d'autre.
Cette méthode n'est pas optimale non plus:
- C'est TRÈS TRÈS lourd à coder
- Pour certains champs, il est normal de pouvoir taper n'importe quel caractère (y compris ceux qui servent souvent lors de l'injection SQL)
La fonction 'prepare'
C'est LA bonne méthode pour se prémunir des injections SQL.
Une injection est rendue possible par le fait que notre script côté serveur HTTP n'envoie la requête SQL qu'après l'avoir "remplie" avec les entrées utilisateurs: le SGBD va donc essayer d'interpréter cette requête-ci, dont le sens à peu être été altéré au moment de l'ajout des entrées utilisateur.
Avec l'instruction "prepare", on envoie la macro au SGBD, qui va interpréter la requête puis attendre les paramètres (les entrées utilisateur) avant de l'exécuter. Quand on envoie au SGBD les paramètres, la requête est déjà compilée, donc quel que soit le contenu de ces derniers, il ne sera jamais considéré comme un bout SQL de la requête mais comme une simple valeur à tester.
L'instruction "prepare" s'utilise comme suit (on reprend notre exemple de chercheur de livre):
<?php {…} //On demande au SGBD de préparer (de précompiler) la requête //Le " ? " indique qu'il y aura un paramètre à cet endroit $smt = $db->prepare ( "SELECT * FROM livres WHERE isbn = ?"); //On renseigne les paramètres $stmt->bindParam(1,$_GET['isbn']) ; //On exécute la requête $stmt->execute() ; {...} ?>
Cette technique offre plusieurs avantages:
- La fonction 'prepare' est proposée par tous les connecteurs, quel que soit le SGBD utilisé
- C'est la seule méthode qui évite à 100% les injections SQL
Bonnes pratiques
Quelques principes à retenir pour éviter les injections SQL, et limiter le pouvoir de l'attaquant en cas d'exploitation:
- Ne jamais faire confiance aux entrées utilisateur, même celles qui à priori ne sont pas modifiées par ce dernier lors d'une utilisation normale de l'application (cookies, champ html 'select', etc.).
- Ne jamais faire utiliser le compte administrateur de la BDD à votre application pour s'y connecter. L'application doit se connecter avec un compte qui a les droits nécessaires à la requête, ni plus ni moins.
- Configurer le report d'erreur pour qu'il soit le moins verbeux possible (inutile de faciliter le travail de l'attaquant !)
- Lancer le SGBD avec un utilisateur dédié qui n'a "aucun" privilège sur le système (pour éviter qu'en cas de corruption du SGBD l'attaquant puisse lancer n'importe quel programme sur la machine).
- Toujours chiffrer / hasher les données sensibles (mots de passe).