« Injections SQL et méthodes de protection » : différence entre les versions
(61 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 8 : | Ligne 8 : | ||
== Qu'est ce que c'est ? == |
== Qu'est ce que c'est ? == |
||
Le |
Le '''SQL''' (Structured Query Language) est un langage informatique permettant d'interagir avec différente base de données relationnelles. |
||
Il permet notamment de créer des tables dans cette base de donnée, ainsi que de les modifier. |
Il permet notamment de créer des tables dans cette base de donnée, ainsi que de les modifier. |
||
Il est consitué de requêtes (query) qui vont interagir avec la base de donnée associée afin d'ajouter ou consulter des informations dans celle-ci. |
Il est consitué de requêtes (query) qui vont interagir avec la base de donnée associée afin d'ajouter ou consulter des informations dans celle-ci. |
||
== Historique du SQL == |
== Historique du SQL == |
||
Ligne 25 : | Ligne 24 : | ||
Il a pas la suite été reconnue comme norme internationale par l'ISO en 1987, et continue d'évoluer régulièrement depuis. |
Il a pas la suite été reconnue comme norme internationale par l'ISO en 1987, et continue d'évoluer régulièrement depuis. |
||
== |
== Injection SQL == |
||
Les injections SQL s’effectue principalement sur des formulaires dans des pages Web. Ces formulaires demande des informations à l'utilisateurs (login, mot de passe...), qui vont être saisie par l'utilisateur. Cette saisie est alors utilisé dans la requète SQL afin de récupérer les informations désirées par l'utilisateurs. Cette requète SQL interagie directement sur la base de donnée du serveur. |
Les injections SQL s’effectue principalement sur des formulaires dans des pages Web. Ces formulaires demande des informations à l'utilisateurs (login, mot de passe...), qui vont être saisie par l'utilisateur. Cette saisie est alors utilisé dans la requète SQL afin de récupérer les informations désirées par l'utilisateurs. Cette requète SQL interagie directement sur la base de donnée du serveur. |
||
[[Fichier:exempleFormulaire.png|500px|thumb|center|Exemple de formulaire]] |
|||
<<Image exemple formulaire>> |
|||
Ligne 36 : | Ligne 35 : | ||
[[Fichier:exempleInjectionSQL.png|500px|thumb|center|Exemple d'injection SQL]] |
|||
<<Image exemple injection dans un formulaire>> |
|||
Ligne 43 : | Ligne 42 : | ||
L'utilisateur va alors pouvoir effectuer diverse actions sur la base de donnée, comme consulter la liste des personnes présente avec leurs informations personnels, ou même la modifier. |
L'utilisateur va alors pouvoir effectuer diverse actions sur la base de donnée, comme consulter la liste des personnes présente avec leurs informations personnels, ou même la modifier. |
||
== Les différents types d’injection == |
|||
=== Connexion sans mot de passe === |
|||
[[Fichier:connexionSansMotDePasse.png|500px|thumb|center|Connexion sans mot de passe]] |
|||
== Quels sont les différents types d’injection ? == |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
<b><u>Connexion sans mot de passe :</u></b> |
|||
SELECT * FROM Users WHERE name='Alice';-- 'AND password = 'xxx'; |
|||
</source> |
|||
<<Image exemple injection dans un formulaire sans mot de passe>> |
|||
<span style="color:green">SELECT</span> * <span style="color:green">FROM</span> Users <span style="color:green">WHERE</span> name='Alice'; <span style="color:grey">-- 'AND password = 'xxx';</span> |
|||
Cette requète signifie qu'on souhaite se connecter avec l'utilisateurs "Alice". Cependant, on tente d'injecter du code SQL afin de pouvoir récupéer les informations d'Alice sans connaitre son mot de passe. Pour cela, on ajoute les caractères : <b>";--"</b> |
Cette requète signifie qu'on souhaite se connecter avec l'utilisateurs "Alice". Cependant, on tente d'injecter du code SQL afin de pouvoir récupéer les informations d'Alice sans connaitre son mot de passe. Pour cela, on ajoute les caractères : <b>";--"</b> |
||
Ligne 57 : | Ligne 56 : | ||
Cela va avoir pour effet de terminer la requète (avec le ";" qui est le symbole de fin de requète), et commenter la suite de la requète (avec le smbole "--"). Cette requète va alors seulement récupérer les informations d'Alice et ne va pas effectuer la vérification du mot de passe, car celui-ci est en commentaire et ne sera donc pas compilé. |
Cela va avoir pour effet de terminer la requète (avec le ";" qui est le symbole de fin de requète), et commenter la suite de la requète (avec le smbole "--"). Cette requète va alors seulement récupérer les informations d'Alice et ne va pas effectuer la vérification du mot de passe, car celui-ci est en commentaire et ne sera donc pas compilé. |
||
=== Connexion avec mot de passe toujours vraie === |
|||
[[Fichier:connexionAvecMotDePasse.png|500px|thumb|center|Connexion sans mot de passe]] |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
<<Image exemple injection dans un formulaire avec mot de passe>> |
|||
SELECT * FROM Users WHERE name='Alice' AND password = 'random' or '1=1';--'; |
|||
</source> |
|||
<span style="color:green">SELECT</span> * <span style="color:green">FROM</span> Users <span style="color:green">WHERE</span> name='Alice' <span style="color:green">AND</span> password = 'random' or '1==1'; <span style="color:grey">--;</span> |
|||
Ici, on injecte du SQL dans le formulaire de saisie du mot de passe. En effet, on tape n'importe quelle mot de passe associé à Alice (vu qu'on ne le connait pas), puis on ajoute : <b>or '1==1';--</b>. |
Ici, on injecte du SQL dans le formulaire de saisie du mot de passe. En effet, on tape n'importe quelle mot de passe associé à Alice (vu qu'on ne le connait pas), puis on ajoute : <b>or '1==1';--</b>. |
||
Ligne 69 : | Ligne 68 : | ||
Ceci a pour effet d'ajouter une condition a notre requète. On vérifie d'abord que le mot de passe d'Alice est correcte, ce qui est bien évidement pas le cas, ou alors on regarde si 1 a bien pour valeurs 1 ce qui est toujours le cas, 1 = 1 donc va renvoyer Vraie. Grace a cette injection, la requète va alors valider la saisie du faux mot de passe, et donc récupérer toute les informations liées à Alice. |
Ceci a pour effet d'ajouter une condition a notre requète. On vérifie d'abord que le mot de passe d'Alice est correcte, ce qui est bien évidement pas le cas, ou alors on regarde si 1 a bien pour valeurs 1 ce qui est toujours le cas, 1 = 1 donc va renvoyer Vraie. Grace a cette injection, la requète va alors valider la saisie du faux mot de passe, et donc récupérer toute les informations liées à Alice. |
||
== Les risques |
== Les risques == |
||
=== Récupération des informations personnels === |
|||
Reprennont la requète précédente : |
Reprennont la requète précédente : |
||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
<span style="color:green">SELECT</span> * <span style="color:green">FROM</span> Users <span style="color:green">WHERE</span> name='Alice'; <span style="color:grey">-- 'AND password = 'xxx';</span> |
|||
SELECT * FROM Users WHERE name='Alice';-- 'AND password = 'xxx'; |
|||
</source> |
|||
Cette requète permet de récupérer toute les informations sur l'utilisateurs Alice. |
Cette requète permet de récupérer toute les informations sur l'utilisateurs Alice. |
||
Ligne 84 : | Ligne 84 : | ||
De plus, certaine informations peuvent être confidentiel, car l'utilisateurs récupère tout, aussi bien son nom que son numéro de carte bleu. |
De plus, certaine informations peuvent être confidentiel, car l'utilisateurs récupère tout, aussi bien son nom que son numéro de carte bleu. |
||
=== Supression de toute la base de donnée === |
|||
[[Fichier:supressionBaseDeDonnee.png|500px|thumb|center|Supression de la base de donnée]] |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
<span style="color:green">SELECT</span> * <span style="color:green">FROM</span> Users <span style="color:green">WHERE</span> name='Alice';<span style="color:green">DROP ALL TABLES</span>;<span style="color:grey">-- 'AND password = 'xxx';</span> |
|||
SELECT * FROM Users WHERE name='Alice'; DROP ALL TABLES;-- 'AND password = 'xxx'; |
|||
</source> |
|||
Voici un autre exemple d'injection de code SQL. Ici, on ne se préoccupe pas des saisies demandé dans les champs. Le but de cette requète est de tout détruire dans la base de donnée. Pour cela on entre un nom d'utilisateurs suivie d'un ";", ce qui va terminer la requète SQL. Puis ensuite on injecte une requète complète dans le formulaire : <b>DROP ALL TABLES</b>. |
Voici un autre exemple d'injection de code SQL. Ici, on ne se préoccupe pas des saisies demandé dans les champs. Le but de cette requète est de tout détruire dans la base de donnée. Pour cela on entre un nom d'utilisateurs suivie d'un ";", ce qui va terminer la requète SQL. Puis ensuite on injecte une requète complète dans le formulaire : <b>DROP ALL TABLES</b>. |
||
Ligne 94 : | Ligne 97 : | ||
== Comment s’en protéger ? == |
== Comment s’en protéger ? == |
||
Il existe plusieurs grands principes de protection contre les injections SQL. |
|||
=== La validation des données === |
|||
La façon de se protéger des injections SQL la plus basique est la validation des données renseignées par l'utilisateur. |
|||
À l'avance, le développeur soit définir les règles qui vont gérer les données qu'il manipule. Il faut que ces règles soient respectées tout au long du processus de saisi jusqu'au stockage dans le base de données. |
|||
Une règle est dans la plupart des cas, la définition d'un certain type pour une donnée. |
|||
Par exemple, on souhaite obtenir la latitude de l'utilisateur. Il faut que le développeur choisisse comment la donnée de la latitude va être stockée. Sous forme de chaîne de caractères ? Sous forme de nombre ? |
|||
Avec ce choix établi, il ne reste plus qu'à mettre en place la validation de cette donnée par rapport au type attendu. |
|||
==== Exemple de validation basique ==== |
|||
Les booléens : vérifier que la donnée saisie est "true" / "false", 0 ou 1. |
|||
Les nombres : détecter que la donnée saisie est un nombre (parsing) |
|||
==== Les dates ==== |
|||
L'une des données essentielle à définir au préalable est l'utilisation des dates. La meilleure manière de gérer les dates est de les stocker en tant que timestamp / nombre. Grâce à cela, il n'y a pas à faire attention au format de date affiché/géré. Mais il existe malgré tout des cas où la date sera stockée en tant que chaîne de caractères. Il est alors primordial de se mettre d'accord sur le format utilisé pour la date. |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
MM/DD/YYYY ou DD/MM/YYYY ou autre... |
|||
</source> |
|||
Devant la multitude de possibilité d'affichage d'une date, il est essentiel de le définir à l'avance. Cette validation s'applique également aux horaires qui sont tout aussi complexe à gérer que les dates. |
|||
==== Les chaînes de caractères ==== |
|||
Le cas d'une chaîne de caractère est complexe. Comme le contenu de cette chaîne dépend directement de l'utilisateur, il faut définir plusieurs règles de validation afin de pouvoir la gérer au mieux. |
|||
===== Échappement des apostrophes ===== |
|||
L'apostrophe est un caractère spécial dans le langage SQL. Le plus généralement, il permet d'entourer une chaîne de caractères. Si la donnée renseignée par l'utilisateur comporte un apostrophe. |
|||
<pre> |
|||
Rue de l'albatros |
|||
</pre> |
|||
Cette apostrophe va potentiellement poser problème lors de l'insertion dans la base de données. |
|||
Pour empêcher cela, il faut "échapper" les apostrophes dans la chaîne de caractères. Cela veut dire que les apostrophes ne seront plus vu directement en tant qu'apostrophe. Pour échapper, la modification suivante va être appliquée : |
|||
<pre> |
|||
' ==> \' |
|||
</pre> |
|||
Cette manière d'échapper les apostrophes est souvent directement mis à disposition pour les programmeurs dans leur langage de programmation utilisé. Par exemple, en PHP, la fonction "mysqli_real_escape_string" permet d'échapper les apostrophes à notre place. |
|||
===== Vérifier la longueur ===== |
|||
Lors de la validation d'une chaîne de caractères, il faut également faire attention à la longueur de cette chaîne. Lorsqu'une colonne est créée dans une base de données pour stocker une chaîne de caractères, la majorité du temps, la type VARCHAR est utilisé. Ce type de donnée force à définir une longueur maximale pour votre chaîne de caractères. |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
VARCHAR(64) |
|||
</source> |
|||
Cela signifie que la longueur maximale pouvant être stockée dans cette colonne pour une chaîne de caractères est de 64 caractères. |
|||
Par rapport à cette contrainte, il faut donc adapter la validation de nos données. Bien vérifier que la longueur de la chaîne renseignée par l'utilisateur ne dépasse pas cette limite ! Sinon, les informations fournies par l'utilisateur pourraient ne pas être stockées dans la base ou peut-être que la chaîne posant problème sera coupée. |
|||
===== Gérer les failles XSS ===== |
|||
Les chaînes de caractères sont à l'origines d'une faille importante sur le web nommée Faille XSS. Cette faille consiste à écrire du code Javascript dans les champs texte accessibles par l'utilisateur. Si ces champs texte impliquent que les données, après soumission, seront affichées sur une page, le code Javascript renseigné sera alors interprété et des actions non voulues pourront se produire à l'encontre du site internet utilisant ces données. |
|||
Le principe de validation pour contrer les failles XSS est assez simple. Il suffit d'échapper les balises HTML afin qu'à l'affichage, les balises ne soit pas vu comme du HTML mais bien comme du texte. De cette manière, on prévient toute utilisation de balise HTML par l'utilisateur et don toute potentiel attaque XSS. |
|||
REF POUR PLUS DE DETAILS SUR LES FAILLES XSS |
|||
==== Les emails ==== |
|||
Les emails sont des chaînes de caractères. Mais comme leur utilisation est bien spécifique dans le monde du web, il leur faut une règle de validation de plus. Comme leur nom l'indique, les emails servent à communiquer avec l'utilisateur par le biais de mails. Il faut donc s'assurer qu'à la saisi par l'utilisateur de son email, il soit valide / bien formé. |
|||
Le problème qui se pose avec les emails est donc de savoir valider la chaîne de caractères pour dire si c'est un email valide ou non. |
|||
La plupart des langages de programmation mettent à disposition des méthodes de validation d'email. Ces méthodes sont très bien pour les emails qui suivent un format assez standard. Mais malheureusement, il est possible que des utilisateurs aient des emails "exotiques". |
|||
[[Fichier:emails_exotiques.png|500px|thumb|center|Exemples d'emails exotiques]] |
|||
Les emails listés ci-dessus sont tous valides, malgré leur syntaxe complexe. Et les fonctions mises à disposition par les langages de programmation ne sont peut-être pas assez efficaces pour détecter ces emails comme valides. |
|||
Vous vous dites certainement que vous pourriez trouver une façon de valider ces 4 emails d'une façon ou d'une autre, mais vous n'êtes pas à l'abri de ne pas pouvoir valider un email alors qu'il est valide. |
|||
La solution à ce problème est plus simple qu'il n'y parait, il suffit tout simplement de ne pas valider les emails ! |
|||
Pour s'assurer que l'email renseigner par l'utilisateur est valide, il suffit de lui envoyer un mail à l'email qu'il a renseigné. Les serveurs de mail se chargeront du reste. |
|||
Par exemple, pour l'inscription d'un utilisateur sur un site. |
|||
Il renseigne son email qui fera office de référence pour son compte. Pour valider son inscription, il suffit de lui envoyer un mail à l'email qu'il a renseigné. Ce mail fera office de validation de son email et lui permettra de confirmer son inscription plutôt que de tenter de détecter si son email est valide ou non. |
|||
Ce système de validation peut également être appliqué aux numéros de téléphone. Envoyer un SMS permettant de valider l'intégrité du numéro de téléphone. |
|||
=== Gérer vos comptes utilisateurs === |
|||
Lors de la création de votre base de données, vous pouvez gérer des comptes utilisateurs. La majorité du temps, le compte utilisé pour accéder à une base de données est le compte par défaut, le compte ROOT. Ce qui peut être une vulnérabilité puisque le compte ROOT a accès en lecture, écriture à toute la base et aux tables. |
|||
Dans certains cas, il n'est pas négligeable de créer un compte utilisateur ayant seulement l'accès en lecture à certaines tables de la base de données. Grâce à cette méthode, vous restreignez l'utilisation de la base de données à seulement l'utilisation que vous voulez avoir avec le compte créé. |
|||
=== Les requêtes paramétrées === |
|||
Les requêtes paramétrées sont des requêtes SQL où des marqueurs de place sont mis pour s'assurer de la forme que la requête SQL aura lors de son exécution. Une requête paramétrée peut être vue comme le template d'une requête. |
|||
Exemple d'implantation |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
reqParam = SELECT * FROM user WHERE lastname = ? AND firstname = ? |
|||
</source> |
|||
Dans cet exemple, on crée une requête paramétrée qui retournera les informations des utilisateurs ayant un nom et un prénom précis. |
|||
Pour intégrer les valeurs à cette requête, on peut utiliser les marqueurs de place (placeholder) comme suit : |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> |
|||
reqParam.setParam( 1, "Alice" ); |
|||
reqParam.setParam( 2, "Case" ); |
|||
</source> |
|||
Grâce à cette syntaxe, la requête aura toujours la bonne forme. Et rien n'empêche de combiner cette syntaxe de requête avec la validation des chaînes de caractères précédemment traitée. |
|||
=== Les procédures stockées === |
|||
Les procédures stockées sont des instructions SQL précompilées du côté de la base de données. |
|||
Pour rendre cette compilation possible, les procédures stockées sont écrites dans un langage dérivé du SQL, le PLSQL. Ce langage de script a été créé par Oracle et est majoritairement présent dans les base de données Oracle. |
|||
L'avantage de ces procédures est qu'elles sont précompilées dans la base de données afin d'être appelées depuis le code. Lors de l'écriture d'une procédure stockée, on lui donne un nom, comme toute procédure (ou fonction). |
|||
Ce nom permet de l'identifier depuis le code et permet également de passer des paramètres. |
|||
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="plsql"> |
|||
CREATE PROC PS_ClientId(@cId int) AS |
|||
BEGIN |
|||
SELECT * FROM User WHERE id = @cId |
|||
END |
|||
</source> |
|||
La grosse différence avec le SQL se passe lors de l'exécution de la requête. |
|||
Le SQL est compilé pendant le "runtime" afin d'exécuter les requêtes souhaitées. C'est de cette étape que vient les vulnérabilité vues précédemment. |
|||
Alors que le PLSQL est déjà compilé. Suivant la procédure, on peut s'attendre à devoir passer des paramètres. Étant donné que la requête est déjà compilée, les paramètres sont ajoutés dans la procédure sans qu'il y ait besoin de réinterpréter la requête. |
|||
C'est cette étape qui sécurise totalement les injections SQL. Les paramètres ne peuvent pas interférer avec la requête compilée. |
|||
== Cas historiques == |
|||
=== Répartitions des différentes techniques d'attaque === |
|||
Malgré les risques connus des injections SQL depuis leur existence, il subsiste toutefois encore trop d'attaque de ce type dans le monde. Comme en témoigne le graphique suivant : |
|||
[[Fichier:Attack_techniques.png|600px|thumb|center|Répartition des différents types d'attaque en Octobre 2014]] |
|||
Il ne devrait pas y avoir autant d'attaques par injection SQL vu les connaissances sur le sujet depuis des années. Faites attention à cela, c'est devenu très facile de se protéger contre les injections SQL de nos jours. |
|||
=== Cas de piratage === |
|||
Ici, deux cas de piratages de logiciels/entreprises connus à cause d'injections SQL. |
|||
#La version '''Microsoft SQL Server 2008''' qui a été victime d'une faille de sécurité en Janvier 2008 a vu des dizaines de milliers de PC se faire infecter par une injection SQL automatique. |
|||
#'''Yahoo!''' a été victime d'une attaque par injection SQL en Juillet 2012. Cette attaque causa le vol de données de plus de 450 000 clients. |
|||
[[Fichier:MSQLServer2008.png|200px|thumb|center|Logo Microsoft SQL Server 2008]] |
|||
[[Fichier:Yahoo.png|200px|thumb|center|Logo Yahoo]] |
Dernière version du 21 novembre 2016 à 22:11
Auteurs : Rémi Rebillard et Loïc Robergeon
Les injections SQL (ou SQLi pour SQL Injection) est une une faille de sécurité dans les formulaires. On injecte dans une requête SQL, un morceau de requête qui n'est pas prévu par le système et va alors renvoyer des informations qui lui sont normalement caché, voir même les modifier.
Qu'est ce que c'est ?
Le SQL (Structured Query Language) est un langage informatique permettant d'interagir avec différente base de données relationnelles.
Il permet notamment de créer des tables dans cette base de donnée, ainsi que de les modifier.
Il est consitué de requêtes (query) qui vont interagir avec la base de donnée associée afin d'ajouter ou consulter des informations dans celle-ci.
Historique du SQL
Créé en Juin 1970 chez IBM par Donald Chamberlin et Ramond Boyce, ce langage a été conçut pour manipuler des bases de données relationnelle via des requètes.
Au départ, le nom était Structured English QUEry Language (SEQUEL) puis a été changé en SQL a cause d'un conflit de marque déposée.
C'est ensuite en 1979 qu'apparait la première version commercialement disponible de SQL dans le marché.
Il a pas la suite été reconnue comme norme internationale par l'ISO en 1987, et continue d'évoluer régulièrement depuis.
Injection SQL
Les injections SQL s’effectue principalement sur des formulaires dans des pages Web. Ces formulaires demande des informations à l'utilisateurs (login, mot de passe...), qui vont être saisie par l'utilisateur. Cette saisie est alors utilisé dans la requète SQL afin de récupérer les informations désirées par l'utilisateurs. Cette requète SQL interagie directement sur la base de donnée du serveur.
Cependant, certains site web sont mal configuré, et des failles apparaisse alors dans ceux-ci. Par exemple, au lieu de rentrer son login dans le précédant formulaire, on rentre d'autres informations comme :
Ces caractères sont spéciaux car ils vont interagir directement sur la requète SQL lié au formulaire. Au lieu de se servir des informations présentes dans le formulaire, les informations vont modifier la requête et retourner des informations qui ne sont pas accessible a l'utilisateurs.
L'utilisateur va alors pouvoir effectuer diverse actions sur la base de donnée, comme consulter la liste des personnes présente avec leurs informations personnels, ou même la modifier.
Les différents types d’injection
Connexion sans mot de passe
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> SELECT * FROM Users WHERE name='Alice';-- 'AND password = 'xxx'; </source>
Cette requète signifie qu'on souhaite se connecter avec l'utilisateurs "Alice". Cependant, on tente d'injecter du code SQL afin de pouvoir récupéer les informations d'Alice sans connaitre son mot de passe. Pour cela, on ajoute les caractères : ";--"
Cela va avoir pour effet de terminer la requète (avec le ";" qui est le symbole de fin de requète), et commenter la suite de la requète (avec le smbole "--"). Cette requète va alors seulement récupérer les informations d'Alice et ne va pas effectuer la vérification du mot de passe, car celui-ci est en commentaire et ne sera donc pas compilé.
Connexion avec mot de passe toujours vraie
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> SELECT * FROM Users WHERE name='Alice' AND password = 'random' or '1=1';--'; </source>
Ici, on injecte du SQL dans le formulaire de saisie du mot de passe. En effet, on tape n'importe quelle mot de passe associé à Alice (vu qu'on ne le connait pas), puis on ajoute : or '1==1';--.
Ceci a pour effet d'ajouter une condition a notre requète. On vérifie d'abord que le mot de passe d'Alice est correcte, ce qui est bien évidement pas le cas, ou alors on regarde si 1 a bien pour valeurs 1 ce qui est toujours le cas, 1 = 1 donc va renvoyer Vraie. Grace a cette injection, la requète va alors valider la saisie du faux mot de passe, et donc récupérer toute les informations liées à Alice.
Les risques
Récupération des informations personnels
Reprennont la requète précédente :
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> SELECT * FROM Users WHERE name='Alice';-- 'AND password = 'xxx'; </source>
Cette requète permet de récupérer toute les informations sur l'utilisateurs Alice.
Par ailleurs, rien ne nous empêche de récupérer les informations d'autre utilisateurs tels que Bob ou Charlie.. On a donc accès a toute les informations sur chaque utilisateurs présent dans la base de donnée.
De plus, certaine informations peuvent être confidentiel, car l'utilisateurs récupère tout, aussi bien son nom que son numéro de carte bleu.
Supression de toute la base de donnée
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> SELECT * FROM Users WHERE name='Alice'; DROP ALL TABLES;-- 'AND password = 'xxx'; </source>
Voici un autre exemple d'injection de code SQL. Ici, on ne se préoccupe pas des saisies demandé dans les champs. Le but de cette requète est de tout détruire dans la base de donnée. Pour cela on entre un nom d'utilisateurs suivie d'un ";", ce qui va terminer la requète SQL. Puis ensuite on injecte une requète complète dans le formulaire : DROP ALL TABLES.
Cette requète a pour effet de supprimer toute les tables présentes dans la base de donnée, donc supprimer toutes les informations présentes dans la base de donnée.
Comment s’en protéger ?
Il existe plusieurs grands principes de protection contre les injections SQL.
La validation des données
La façon de se protéger des injections SQL la plus basique est la validation des données renseignées par l'utilisateur.
À l'avance, le développeur soit définir les règles qui vont gérer les données qu'il manipule. Il faut que ces règles soient respectées tout au long du processus de saisi jusqu'au stockage dans le base de données.
Une règle est dans la plupart des cas, la définition d'un certain type pour une donnée. Par exemple, on souhaite obtenir la latitude de l'utilisateur. Il faut que le développeur choisisse comment la donnée de la latitude va être stockée. Sous forme de chaîne de caractères ? Sous forme de nombre ?
Avec ce choix établi, il ne reste plus qu'à mettre en place la validation de cette donnée par rapport au type attendu.
Exemple de validation basique
Les booléens : vérifier que la donnée saisie est "true" / "false", 0 ou 1.
Les nombres : détecter que la donnée saisie est un nombre (parsing)
Les dates
L'une des données essentielle à définir au préalable est l'utilisation des dates. La meilleure manière de gérer les dates est de les stocker en tant que timestamp / nombre. Grâce à cela, il n'y a pas à faire attention au format de date affiché/géré. Mais il existe malgré tout des cas où la date sera stockée en tant que chaîne de caractères. Il est alors primordial de se mettre d'accord sur le format utilisé pour la date.
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> MM/DD/YYYY ou DD/MM/YYYY ou autre... </source>
Devant la multitude de possibilité d'affichage d'une date, il est essentiel de le définir à l'avance. Cette validation s'applique également aux horaires qui sont tout aussi complexe à gérer que les dates.
Les chaînes de caractères
Le cas d'une chaîne de caractère est complexe. Comme le contenu de cette chaîne dépend directement de l'utilisateur, il faut définir plusieurs règles de validation afin de pouvoir la gérer au mieux.
Échappement des apostrophes
L'apostrophe est un caractère spécial dans le langage SQL. Le plus généralement, il permet d'entourer une chaîne de caractères. Si la donnée renseignée par l'utilisateur comporte un apostrophe.
Rue de l'albatros
Cette apostrophe va potentiellement poser problème lors de l'insertion dans la base de données.
Pour empêcher cela, il faut "échapper" les apostrophes dans la chaîne de caractères. Cela veut dire que les apostrophes ne seront plus vu directement en tant qu'apostrophe. Pour échapper, la modification suivante va être appliquée :
' ==> \'
Cette manière d'échapper les apostrophes est souvent directement mis à disposition pour les programmeurs dans leur langage de programmation utilisé. Par exemple, en PHP, la fonction "mysqli_real_escape_string" permet d'échapper les apostrophes à notre place.
Vérifier la longueur
Lors de la validation d'une chaîne de caractères, il faut également faire attention à la longueur de cette chaîne. Lorsqu'une colonne est créée dans une base de données pour stocker une chaîne de caractères, la majorité du temps, la type VARCHAR est utilisé. Ce type de donnée force à définir une longueur maximale pour votre chaîne de caractères.
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> VARCHAR(64) </source> Cela signifie que la longueur maximale pouvant être stockée dans cette colonne pour une chaîne de caractères est de 64 caractères.
Par rapport à cette contrainte, il faut donc adapter la validation de nos données. Bien vérifier que la longueur de la chaîne renseignée par l'utilisateur ne dépasse pas cette limite ! Sinon, les informations fournies par l'utilisateur pourraient ne pas être stockées dans la base ou peut-être que la chaîne posant problème sera coupée.
Gérer les failles XSS
Les chaînes de caractères sont à l'origines d'une faille importante sur le web nommée Faille XSS. Cette faille consiste à écrire du code Javascript dans les champs texte accessibles par l'utilisateur. Si ces champs texte impliquent que les données, après soumission, seront affichées sur une page, le code Javascript renseigné sera alors interprété et des actions non voulues pourront se produire à l'encontre du site internet utilisant ces données.
Le principe de validation pour contrer les failles XSS est assez simple. Il suffit d'échapper les balises HTML afin qu'à l'affichage, les balises ne soit pas vu comme du HTML mais bien comme du texte. De cette manière, on prévient toute utilisation de balise HTML par l'utilisateur et don toute potentiel attaque XSS.
REF POUR PLUS DE DETAILS SUR LES FAILLES XSS
Les emails
Les emails sont des chaînes de caractères. Mais comme leur utilisation est bien spécifique dans le monde du web, il leur faut une règle de validation de plus. Comme leur nom l'indique, les emails servent à communiquer avec l'utilisateur par le biais de mails. Il faut donc s'assurer qu'à la saisi par l'utilisateur de son email, il soit valide / bien formé.
Le problème qui se pose avec les emails est donc de savoir valider la chaîne de caractères pour dire si c'est un email valide ou non.
La plupart des langages de programmation mettent à disposition des méthodes de validation d'email. Ces méthodes sont très bien pour les emails qui suivent un format assez standard. Mais malheureusement, il est possible que des utilisateurs aient des emails "exotiques".
Les emails listés ci-dessus sont tous valides, malgré leur syntaxe complexe. Et les fonctions mises à disposition par les langages de programmation ne sont peut-être pas assez efficaces pour détecter ces emails comme valides.
Vous vous dites certainement que vous pourriez trouver une façon de valider ces 4 emails d'une façon ou d'une autre, mais vous n'êtes pas à l'abri de ne pas pouvoir valider un email alors qu'il est valide.
La solution à ce problème est plus simple qu'il n'y parait, il suffit tout simplement de ne pas valider les emails !
Pour s'assurer que l'email renseigner par l'utilisateur est valide, il suffit de lui envoyer un mail à l'email qu'il a renseigné. Les serveurs de mail se chargeront du reste.
Par exemple, pour l'inscription d'un utilisateur sur un site. Il renseigne son email qui fera office de référence pour son compte. Pour valider son inscription, il suffit de lui envoyer un mail à l'email qu'il a renseigné. Ce mail fera office de validation de son email et lui permettra de confirmer son inscription plutôt que de tenter de détecter si son email est valide ou non.
Ce système de validation peut également être appliqué aux numéros de téléphone. Envoyer un SMS permettant de valider l'intégrité du numéro de téléphone.
Gérer vos comptes utilisateurs
Lors de la création de votre base de données, vous pouvez gérer des comptes utilisateurs. La majorité du temps, le compte utilisé pour accéder à une base de données est le compte par défaut, le compte ROOT. Ce qui peut être une vulnérabilité puisque le compte ROOT a accès en lecture, écriture à toute la base et aux tables.
Dans certains cas, il n'est pas négligeable de créer un compte utilisateur ayant seulement l'accès en lecture à certaines tables de la base de données. Grâce à cette méthode, vous restreignez l'utilisation de la base de données à seulement l'utilisation que vous voulez avoir avec le compte créé.
Les requêtes paramétrées
Les requêtes paramétrées sont des requêtes SQL où des marqueurs de place sont mis pour s'assurer de la forme que la requête SQL aura lors de son exécution. Une requête paramétrée peut être vue comme le template d'une requête.
Exemple d'implantation
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> reqParam = SELECT * FROM user WHERE lastname = ? AND firstname = ? </source>
Dans cet exemple, on crée une requête paramétrée qui retournera les informations des utilisateurs ayant un nom et un prénom précis.
Pour intégrer les valeurs à cette requête, on peut utiliser les marqueurs de place (placeholder) comme suit :
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="sql"> reqParam.setParam( 1, "Alice" ); reqParam.setParam( 2, "Case" ); </source>
Grâce à cette syntaxe, la requête aura toujours la bonne forme. Et rien n'empêche de combiner cette syntaxe de requête avec la validation des chaînes de caractères précédemment traitée.
Les procédures stockées
Les procédures stockées sont des instructions SQL précompilées du côté de la base de données.
Pour rendre cette compilation possible, les procédures stockées sont écrites dans un langage dérivé du SQL, le PLSQL. Ce langage de script a été créé par Oracle et est majoritairement présent dans les base de données Oracle.
L'avantage de ces procédures est qu'elles sont précompilées dans la base de données afin d'être appelées depuis le code. Lors de l'écriture d'une procédure stockée, on lui donne un nom, comme toute procédure (ou fonction). Ce nom permet de l'identifier depuis le code et permet également de passer des paramètres.
<source style="border: 1px dashed #2f6fab;background-color: #f9f9f9;font-size: 127%;padding: 1em;" lang="plsql"> CREATE PROC PS_ClientId(@cId int) AS BEGIN SELECT * FROM User WHERE id = @cId END </source>
La grosse différence avec le SQL se passe lors de l'exécution de la requête.
Le SQL est compilé pendant le "runtime" afin d'exécuter les requêtes souhaitées. C'est de cette étape que vient les vulnérabilité vues précédemment.
Alors que le PLSQL est déjà compilé. Suivant la procédure, on peut s'attendre à devoir passer des paramètres. Étant donné que la requête est déjà compilée, les paramètres sont ajoutés dans la procédure sans qu'il y ait besoin de réinterpréter la requête. C'est cette étape qui sécurise totalement les injections SQL. Les paramètres ne peuvent pas interférer avec la requête compilée.
Cas historiques
Répartitions des différentes techniques d'attaque
Malgré les risques connus des injections SQL depuis leur existence, il subsiste toutefois encore trop d'attaque de ce type dans le monde. Comme en témoigne le graphique suivant :
Il ne devrait pas y avoir autant d'attaques par injection SQL vu les connaissances sur le sujet depuis des années. Faites attention à cela, c'est devenu très facile de se protéger contre les injections SQL de nos jours.
Cas de piratage
Ici, deux cas de piratages de logiciels/entreprises connus à cause d'injections SQL.
- La version Microsoft SQL Server 2008 qui a été victime d'une faille de sécurité en Janvier 2008 a vu des dizaines de milliers de PC se faire infecter par une injection SQL automatique.
- Yahoo! a été victime d'une attaque par injection SQL en Juillet 2012. Cette attaque causa le vol de données de plus de 450 000 clients.