Les injections SQL

Explications et prévention sur les injections SQL

Pour ce premier article, j'ai décidé d'aborder une faille classique et malheureusement encore trop présente : les injections SQL.

Le but de cet article n'est pas d'apprendre à pirater un site (ce qui, comme vous vous en doutez, est illégal) mais plus à comprendre le problème afin de s'en prémunir. Il s'agit donc plus d'une introduction plutôt qu'un tutoriel détaillant comment faire une injection SQL.

Qu'est-ce qu'une injection SQL ?

Une injection SQL, comme son nom l'indique, consiste à injecter du code SQL dans une donnée afin de continuer ou plutôt de détourner la requête et de lui faire faire autre chose que ce pour quoi elle a été conçue. Cela permet de manipuler la base de données et d'accéder, par exemple, à des données "normalement" inaccessibles (tables des utilisateurs avec tout ce qu'elle contient : login, mot de passe, adresse mail etc…) ou encore d'effectuer des opérations qu'un utilisateur classique ne devrait pas pouvoir faire (suppression de la base de données, ajout/modification d'enregistrements, création/lecture de fichiers etc…).

Passons maintenant à la démonstration de 2 cas.

Premier cas : injection SQL sur une chaîne de caractères

En langage SQL, une chaîne de caractère est entourées de guillemets (simples ou doubles).

Examinons la requête suivante :

1
$query = "SELECT id, titre, texte FROM articles WHERE titre LIKE '%".$_GET['titre']."%'"; 

Cette requête va rechercher l'article dont le titre contient le terme envoyé par la variable titre, variable de type GET (mais ça aurait pu être POST, cela ne change rien).

Une variable de type GET est présente dans l'URL sous la forme nom_variable=valeur.

Imaginons maintenant que nous avons un article intitulé "L'article de la semaine". Nous allons le rechercher en utilisant le mot "article" :

1
http://www.monsite.com/article.php?titre=article

Ce qui donnera la requête SQL suivante :

1
SELECT id, titre, texte FROM articles WHERE titre LIKE '%article%' 

Rien à signaler, tout se passe comme prévu.

Maintenant tentons de le rechercher en utilisant cette fois le mot "L'article"

1
http://www.monsite.com/article.php?titre=L'article

Et là … nous avons une erreur !

Pourquoi ?

Regardons nos 2 requêtes SQL de plus près…

Premier cas :

1
SELECT id, titre, texte FROM articles WHERE titre LIKE '%article%' 

Second cas :

1
SELECT id, titre, texte FROM articles WHERE titre LIKE '%L'article%' 

Avez-vous trouvé l'erreur ou plutôt ce qui cause cette erreur ?

Le responsable est en fait notre guillemet présent dans le terme recherché. Ce guillemet va être considéré comme la fin de la chaîne de caractère et ce qui suit va être interprété comme du code SQL. La commande article%' n'existant pas, cela produit une erreur.

Mais que se passerait-t-il si la commande existait et que la requête serait syntaxiquement juste ?

La réponse est simple : il n'y aurait aucune erreur et la requête serait interprétée.

Essayons d'injecter ceci :

1
http://www.monsite.com/article.php?titre=article' AND 1=1 --

Pas d'erreur et l'article s'affiche bien.

Le - - à la fin est un commentaire. En effet, si nous ne le mettons pas il restera un bout de la requête initiale, ce qui risque fort de provoquer une erreur de syntaxe… à moins que l'on s'arrange pour que notre injection concorde syntaxiquement.

Mais l'on peut faire beaucoup plus simple : s'arranger pour ignorer totalement cette partie et c'est précisément à ça que sert ce commentaire placé juste après notre injection.

Tentons maintenant cela :

1
http://www.monsite.com/article.php?titre=article' AND 1=2 --

De nouveau pas d'erreur… mais cette fois l'article ne s'affiche pas.

Cela n'a rien de magique si l'on y réfléchit bien : la première condition est vérifiée, la seconde par contre ne l'est pas (1 n'est pas égal à 2). Le AND exige que les 2 conditions renvoient TRUE pour que le tout le soit, or là nous avons une condition renvoyant FALSE d'où l'absence de résultat. Ceci étant dit la requête est syntaxiquement juste et la présence ou l'absence de résultat nous prouve que le code SQL a bien été exécuté et qu'il est donc possible d'en injecter afin de détourner la requête initiale.

Comment s'en protéger ?

Pour le cas des chaînes de caractères, il faut les échapper comme on dit. C'est à dire qu'un antislash ( \ ) sera ajouté devant les caractères potentiellement dangereux comme les guillemets ainsi que d'autres caractères. Cet antislash signifiera que le caractère qui suit doit être interprété comme du simple texte. Le guillemet injecté ne sera donc plus considéré comme la fin de la chaîne mais comme un guillemet. Il sera donc impossible au pirate de fermer la chaîne et par conséquent, impossible d'exécuter son injection SQL.

Pour ça, il faut utiliser des fonctions comme mysqli_real_escape_string() ou la préparation de requête si vous utilisez des outils comme PDO (encore une fois, à adapter si vous utilisez autre chose).

Nous avons abordé le cas d'une injection SQL sur une chaîne de caractères mais ce n'est pas le seul existant. Dans l'exemple suivant, vous comprendrez que l'échappement des chaînes n'est pas forcément suffisant pour contrer ce type d'attaque.

Second cas : injection SQL sur un nombre

L'échappement est une bonne chose mais n'est pas toujours suffisant et nous allons en apporter la preuve avec cet exemple.

Cette fois nous rechercherons notre article sur la base de son identifiant, identifiant qui est un nombre entier.

1
$query = "SELECT id, titre, texte FROM articles WHERE id = ".$_GET['id']; 

Cette requête va afficher l'article correspondant à l'id qu'on lui a passé en paramètre.

1
http://www.monsite.com/article.php?id=1

L'article possédant l'id 1 sera affiché.

1
SELECT id, titre, texte FROM articles WHERE id = 1 

La principale différence avec une injection sur une chaîne de caractères réside dans le fait qu'un nombre n'a pas forcément besoin d'être entouré de guillemets (bien qu'il peut l'être). Le pirate n'a donc ici plus besoin de chercher à fermer la chaîne. Il peut directement injecter du code SQL après l'identifiant.

De ce fait, tant qu'il n'utilise pas de chaînes de caractères (et donc des guillemets) son injection sera prise en considération sans aucun problème. L'échappement n'est donc pas suffisant dans ce cas ci pour se protéger.

Exemple :

1
http://www.monsite.com/article.php?id=1 AND 1=2 --

ce qui donnera :

1
SELECT id, titre, texte FROM articles WHERE id = 1 AND 1=2 --

Aucun guillemet n'a été utilisé et notre requête a bien été exécutée. ;)

Comment alors réellement s'en protéger dans tous les cas ?

Il y a un dicton dans le milieu informatique qui dit :

Ne faites JAMAIS confiance aux données provenant de l'utilisateur !

En bref, vous DEVEZ vérifier que la donnée reçue correspond bien à ce que vous attendez et prévoir que l'utilisateur peut tenter d'injecter des caractères non prévus (caractères alphanumériques, guillemets, slash, signes de ponctuation, etc.). Ces caractères ne doivent pas affecter le comportement de votre requête sinon ça signifie qu'il est potentiellement probable qu'on puisse la détourner.

Par exemple, si l'identifiant de votre article est un nombre et que vous savez que ça sera toujours un nombre, alors vérifiez que la variable reçue est bien un nombre (en PHP cela peut se faire notamment avec la fonction is_numeric). Si vous recevez une chaîne de caractères, assurez-vous de la gérer et ne permettez pas à l'utilisateur de pouvoir fermer cette chaîne et, par conséquent, d'injecter ce qu'il veut à la suite (en bref : échappez-là).

Les liens qui suivent se rapportent à MySQL et à PHP mais des fonctions similaires devraient logiquement exister pour les autres langages/SGBD.

MySQLi : http://php.net/manual/fr/mysqli.real-escape-string.php

PDO : http://php.net/manual/fr/pdo.quote.php

PDO et requêtes préparées : http://php.net/manual/fr/pdo.prepare.php

Ce n'est bien souvent pas grand chose mais une erreur de ce type peut vous coûter très cher. Et contrairement à ce que l'on peut croire, ça ne touche pas que les "petits" sites. De nombreux outils très répandus sont touchés par ce fléau : on peut citer notamment Wordpress, Joomla et plus récemment Drupal (ce qui représente juste … quelques millions de sites :euh: ).


Les injections SQL sont, comme beaucoup d'autres failles, dues à un manque de vérification de la part du développeur. Elles peuvent mener à des conséquences désastreuses et beaucoup d'entreprises ont notamment été victimes de chantage suite au vol de leur base de données.

De plus, les logiciels automatisant cette attaque sont de plus en plus nombreux et par conséquent le nombre d'attaquants également plus élevé.

Il n'est donc pas étonnant que cette faille se retrouve dans le top du classement des failles web. Elle peut être malgré tout facilement évitée avec de bonnes pratiques comme nous venons de le voir. ;)

39 commentaires

Très bon article, très intéressant.

Petite question, cependant, la méthode PDO::prepare() de PHP permet-elle de gérer le cas de l'injection sur un nombre ?

Je me suis laissé dire que puisque PDO a accès à la base de données, il pouvait peut-être vérifier la concordance des données reçues, mais je me trompe peut-être :-/

/me part tenter de pirater son propre site…

+3 -0

Bon article : clair, court et simple. :)

Peut-être eut-il fallu donner quand même des exemples de code malicieux, histoire de faire comprendre la gravité de la chose.

Sinon, le 2e cas n'est peut-être pas très intéressant en soi, puisque l'on peut le ramener au premier en écrivant le nombre avec des guillemets.

Au fait, une illustration sympa : http://xkcd.com/327/

+2 -0

Petite question, cependant, la méthode PDO::prepare() de PHP permet-elle de gérer le cas de l'injection sur un nombre ?

Jérôme Deuchnord

bindParam() ?

Titi_Alone

Je vais me documenter là-dessus, merci :)

Excellent article ! … mais ça me fait peur : j'ai un blog wordpress : en tant qu'administrateur lambda, est-ce que je peux faire qq chose pour me protéger ?

rozo

Logiquement, si WordPress a bien été codé, il ne devrait pas y avoir ce genre de soucis :-°

+4 -0

Pour un premier article, ça fait plaisir de voir qu'il est apprécié. ^^

Excellent article ! … mais ça me fait peur : j'ai un blog wordpress : en tant qu'administrateur lambda, est-ce que je peux faire qq chose pour me protéger ?

rozo

Bien faire les mises à jour, vérifier que les plugins utilisés n'en contiennent pas (taper le développeur (mais pas trop fort quand même) si c'est le cas). Sinon une petite recherche du genre "wordpress numero_de_la_version vulnerabilities" et tu devrais assez vite trouver ce qu'il y a comme vulnérabilités répertoriées pour la version en question. ;-)

Sinon, le 2e cas n'est peut-être pas très intéressant en soi, puisque l'on peut le ramener au premier en écrivant le nombre avec des guillemets.

Effectivement c'est une possibilité mais je voulais vraiment aborder le fait qu'une protection sur un type précis de données ne protège pas forcément de tous les autres et qu'il faut globalement toujours vérifier les données reçues. Ce cas illustrait parfaitement cette problématique.

Petite question, cependant, la méthode PDO::prepare() de PHP permet-elle de gérer le cas de l'injection sur un nombre ?

Je me suis laissé dire que puisque PDO a accès à la base de données, il pouvait peut-être vérifier la concordance des données reçues, mais je me trompe peut-être :-/

Non, tu ne te trompes pas. ;-)

PDO gère tous les cas de figure (si tu le lui demande du moins). Tu définis dans ta requête des "marqueurs" en les faisant précéder du symbole : et ensuite avec bindParam tu peux préciser le type de données pour chacun de ces marqueurs (PDO::PARAM:INT pour un entier, PDO::PARAM:STR pour une chaîne de caractères etc). PDO se chargera alors du reste.

Tu as plus d'explications ici: http://php.net/manual/fr/pdostatement.bindparam.php

+4 -0

Excellent article ! … mais ça me fait peur : j'ai un blog wordpress : en tant qu'administrateur lambda, est-ce que je peux faire qq chose pour me protéger ?

Tu peux tester ton site avec wpscan, mais même si tu trouve une faille ,sauf si c'est dans un plugin (à ce moment tu le désinstalle en attendant une MAJ) tu ne peux pas vraiment te protéger. Il faut mettre à jour wordpress, tes thèmes et tes plugins. Tu peux supprimer ou modifier certain fichiers qui donnent des informations sur ton site comme http://tonsite.com/robots.txt ou (pour wordpress) http://tonsite.com/readme.html Il me semble que tu peux changer l'url de la page de login pour mettre des battons dans les roues des hackers.

EDIT : en effet tu peux : https://wpchannel.com/modifier-url-connexion-administration-wordpress/

cordialement D3m0t3p

+2 -0

Hello, pas mal l'article :) . J'ai juste une légère remarque: on dirait que les injections SQL sont cantonnées à PHP.

Alors si tu ne connais bien que PHP, c'est pas bien grave que tu fasses un article en citant les fonctions de PHP (d'autant plus que c'est le language qui attire le plus les novices donc ça se tient), en revanche tu pourrais dire qu'il faut faire attention dans les autres langages, ça ne mange pas de pain !

Hello, pas mal l'article :) . J'ai juste une légère remarque: on dirait que les injections SQL sont cantonnées à PHP.

Alors si tu ne connais bien que PHP, c'est pas bien grave que tu fasses un article en citant les fonctions de PHP (d'autant plus que c'est le language qui attire le plus les novices donc ça se tient), en revanche tu pourrais dire qu'il faut faire attention dans les autres langages, ça ne mange pas de pain !

Nek

Effectivement ça ne concerne absolument pas que PHP mais bien tous les langages qui permettent de traiter des requêtes SQL (à peu près tous quoi ^^) mais PHP est celui que je maitrise le mieux. C'est vrai que ça n’apparaît pas "clairement" dans l'article.

Très bon article et très accessible.

Notons que c'est un des avantages des Framework, ils permettent d'éviter au maximum ce genre de failles. ZdS par exemple qui est codé avec Django est immunisé contre ça (mais ça c'est aussi parce qu'on a des devs consciencieux)

firm1

Ah c'est clair qu'avec un bon framework, on peut balancer tout ce que l'on veut ce n'est pas prêt de fonctionner. ^^ Le seul problème, comme l'a précisé le validateur qui s'est occupé de cet article, c'est quand on revient "à l'ancienne" … et qu'on en oublie de sécuriser ses variables. :P

Sinon il est clair que les (bons) frameworks actuels annihilent complétement ce type d'attaque.

Bonjour.

Merci pour l'article. Penses-tu évoquer un cas pratique à l'avenir ? Par le biais d'un tutoriel par exemple ? Cela pourrait présenter un grand intérêt pour le public visé. :)

30b830e7

Ah si ça intéresse vraiment je ne suis absolument pas contre ! ^^

A voir après si le Staff accepterait que je montre quelques exemples concrets car ce genre de matière est toujours un peu délicat à aborder, surtout quand on veut pousser plus loin.

J'ai bien aimé. Dommage que ce ne soit qu'une introduction ;)

Petite question: cette attaque est-elle spécifique aux BDD de sites web ou peut-on aussi "détourner" les requêtes avec des BDD de logiciels?

+2 -0

J'sais pas trop. Certains bloggeurs ont eu quelques mésaventures récemment. :-°

Arius

Je doute que ce soit une raison suffisante pour renoncer.

On a des articles sur la rétro-ingénierie de binaires et l'exploitation de vulnérabilité sur Zeste de Savoir. De ce fait, je ne pense pas que cet argument soit recevable.

Petite question: cette attaque est-elle spécifique aux BDD de sites web ou peut-on aussi "détourner" les requêtes avec des BDD de logiciels?

Qu'entends-tu par "BDD" de logiciels ? Une application web, en soit, c'est "un logiciel".

Si tu fais références à des interfaces graphiques…? La réponse est oui. Tout dépend comment l'implémentation a été réalisée.

la majorité du temps les systèmes de GUI sont des projets en soit alors on essaie de limiter le besoin en expertise dans d'autres techno telles que le SQL. Du coup la majorité des framework graphique sont intégrés dans un tout qui possède au pire un QueryBuilder (c'est à dire un programme qui vous permet de générer des requêtes à la volée en échappant ce qui doit l'être, en validant l'aspect correct de chaque argument…) au mieux un ORM (object to relationnal mapping, tu peux trouver un petit exemple dans ce tuto). Ces outils, tant que tu n'essaies pas d'en contourner le fonctionnement en faisant des requêtes "en dur", t'éviteront tous les problèmes d'injection SQL.

Cela ne signifie pas que ton application est foncièrement sécurisée (un stockage hasardeux, un manque de réalisme dans les contraintes imposées au données sont des pièges sécuritaires bien plus vicieux et graves qu'on a tendance à le penser), par contre les injections SQL sont mis hors d'atteinte.

+2 -0

Très bon article, mais je pense qu'il aurait été préférable d'insister davantage sur l'intérêt d'utiliser un outil de préparation des requêtes, et de déconseiller coûte que coûte la construction de requêtes SQL par simple concaténation de données saisie par l'utilisateur (même échappées).

+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte