Par le passé, j’ai remarqué qu’un nombre non négligeable de développeurs web n’étaient pas du tout sensibilisés à la sécurité de leurs développements. Cela ne leur avait jamais été présenté comme une notion importante à prendre en charge, ni pendant leurs études, ni pendant leurs précédentes expériences professionnelles, ni dans leur propre veille techno.
Je comprends l’argument qui est de dire que la sécurité informatique est un métier à part entière, et qu’il est très compliqué pour un « simple » développeur d’être un expert sur ce sujet. On est d’accord que les hackers sont nombreux et certains ont des moyens importants à leur disposition.
C’est un peu comme pour la sécurité d’une maison : si vous laissez votre porte grande ouverte, vous multipliez le risque de cambriolage dans des proportions ahurissantes. Par contre, plus vous sécurisez votre habitation (porte blindée, fenêtre anti-intrusion, alarme…), plus ça prendra de temps pour la forcer. Évidemment, un voleur déterminé réussira toujours à y entrer, aucun système n’est sûr à 100%. Mais plus ce sera compliqué, plus vous aurez de chance que le voleur préfère passer à une autre maison.
Jusqu’à un certain point, c’est un peu pareil avec la sécurité informatique.
Il existe quelques failles de sécurité basiques, connues depuis très longtemps, qui sont assez faciles à prendre en compte.
Gardez en tête que :
- Ne pas les gérer serait une erreur monumentale. Les hackers ont des programmes qui tentent de pénétrer des systèmes de manière automatisée. Si vous n’êtes pas blindé sur ces failles basiques, le moindre script kiddie pourra infecter vos serveurs.
- C’est bien plus simple, plus rapide et moins coûteux de les gérer dès le début du développement. Reprendre un développement complet afin de le sécuriser est un travail long et minutieux (et rébarbatif), qui peut laisser passer des trous de sécurité. Et un vrai audit de sécurité peut coûter très cher.
Le principe de l’injection SQL
Commençons avec la plus basique de toutes les failles, et sûrement la plus connue, l’injection SQL. Elle représente encore un enjeu important, puisque deux tiers des attaques sur le web portent dessus.
L’idée est de réussir à faire exécuter des requêtes SQL par un serveur, en faisant ce qu’il faut pour altérer la requête. Suivant les cas, connaître le type de serveur (MySQL, PostgreSQL, Oracle, etc.) ou le schéma de la base pourra grandement faciliter les choses.
Imaginons que vous fassiez un formulaire permettant de se désinscrire d’une newsletter. Vous proposez un champ pour que l’utilisateur fournisse son adresse email, et quand le formulaire est envoyé au serveur, vous utilisez cette adresse dans la requête.
Ainsi, vous exécuterez une requête du genre :
DELETE FROM Abonnement WHERE email = '[email protected]';
Mais imaginons que l’utilisateur soit un peu pernicieux, et qu’au lieu de saisir une adresse email, il remplisse le champ avec la chaîne de caractères suivante :
' OR TRUE --
Le serveur comprendra donc que la requête est :
DELETE FROM Abonnement WHERE email = '' OR TRUE --';
Je rappelle que “–” indique le début d’un commentaire ; tout le reste de la ligne sera ignoré. La partie WHERE contient maintenant deux évaluations ; la première qui recherche les lignes dont l’adresse email est vide, et la seconde qui sera toujours vraie. Le reste de la ligne étant commenté (par le “–”), il ne sera pas pris en compte.
Vous avez compris, le résultat est que toute la base est effacée.
Autre exemple : Imaginons que vous ayez un formulaire d’authentification, avec un champ “login” et un champ “mot de passe”. Pour vérifier un utilisateur, on va faire une requête qui va chercher l’identifiant de l’utilisateur avec ce login et ce mot de passe (après un hachage MD5).
La requête serait alors :
SELECT id FROM User WHERE login = 'yoda' AND pwd = MD5('maitrejedi');
Mais imaginons que le login envoyé soit la chaîne suivante :
yoda' --
La requête finale, telle que la comprendra le serveur, sera :
SELECT id FROM User WHERE login = 'yoda' --' AND pwd = MD5('');
Le résultat sera que la personne est authentifiée alors qu’elle ne connaît pas le mot de passe correct.
Les solutions contre l’injection SQL
Il existe plusieurs solutions contre les injections SQL.
De manière générale, il faut que les requêtes exécutées par le serveur soient nettoyées, afin qu’elles ne contiennent pas d’instructions SQL non désirées.
La première manière d’y arriver est de traiter les données venant de l’extérieur, pour que les caractères spéciaux soient échappés. Il existe des fonctions servant à cela, en fonction de votre langage de programmation (mysqli_real_escape_string en PHP, MySQLdb.escape_string en Python, mysql.escape en NodeJS, etc.).
Si on reprend le dernier exemple, la requête générée après échappement des données entrantes deviendrait alors :
SELECT id FROM User
WHERE login = 'yoda\' --' AND pwd = MD5('');
Le caractère backslash (« \ ») inséré devant l’apostrophe fait que celle-ci n’est pas vue comme marquant la fin de la chaîne. La requête s’exécutera alors en cherchant effectivement un utilisateur dont le login est “yoda’ –” avec un mot de passe vide, et ne retournera aucun résultat.
Une autre manière de traiter les injections SQL est d’utiliser des requêtes préparées, pour les systèmes qui le proposent (la grande majorité).
Dans ce cas, on va écrire une requête paramétrable, où les valeurs sont remplacées par des labels. À l’exécution de la requête, on fournit les valeurs sous forme de paramètres qui vont remplacer les labels, en étant automatiquement échappés.
Un exemple en PHP avec la classe PDO :
$req = $db->prepare('DELETE FROM Abonnement WHERE email = :email');
$req->execute([':email' => '[email protected]']);
Un autre moyen de contourner le problème est de ne pas écrire de SQL. Avec un ORM, vous écrivez du code natif dans votre langage de programmation, et c’est la couche d’abstraction qui se débrouille pour générer du SQL correct.