Dans l'actualité informatique, nous voyons souvent apparaître vols de données et injections SQL.
Ce type de vulnérabilité fait partie des plus dangereuses et des plus répandues.
Nous allons voir comment les détecter et s'en protéger.
Comme tout sujet complexe, quelques définitions sont nécessaires.
Une base de donnée, comme son nom l'indique, est un outil permettant de stocker des données. Dans la plupart des applications, une base de donnée stocke les informations nécessaires au bon fonctionnement de l'outil.
Par exemple pour un site web, il faut stocker les comptes utilisateurs, les pages, les cartes bancaires, etc...
Pour un jeu vidéo, il faut sauvegarder l'inventaire d'un avatar et ses caractéristiques, les informations du joueur, etc...
La base de donnée est un outil puissant, stratégique et donc hautement convoité.
SQL est l'abréviation de Structured Query Language (langage de requête structurée). C'est un langage de programmation permettant de faire des requêtes à une base de donnée relationnelle pour obtenir les informations souhaitées.
Pour comprendre la suite de cet article, il est vivement conseillé d'avoir des bases dans ce langage.
Une injection SQL est la modification d'une requête envoyée à la base de donnée pour en détourner l'utilisation. Il est alors possible de se connecter à un site en tant qu'administrateur, de modifier le prix des articles que l'on achète, de tricher à un jeu vidéo, etc...
Ce type de faille est beaucoup plus courante sur les applications web.
Tout au long de l'article, nous allons donc nous concentrer sur les applications web, sachant que le principe est le même pour tous types d'application.
J'illustrerai un maximum les exemples par des images ou des liens vers un SQL Fiddle (site permettant de tester les requêtes SQL).
Lorsque le client veut accéder à un site, il interroge le serveur correspondant grâce à son navigateur.
Ensuite, le serveur traite sa demande, sollicite la base de donnée pour construire la page web qu'il retourne au client.
Enfin, le navigateur du client reçoit la page et l'affiche.
Voici une petite image qui récapitule très sommairement l'interaction d'un client avec un site web et la base de donnée.
Maintenant, en s'appuyant sur l'image précédente, prenons un exemple un peu plus poussé pour illustrer une injection SQL :
Si nous ajoutons un peu de SQL à cet exemple, la requête SQL sera donc du genre :
SELECT * FROM ARTICLES WHERE published = 1 AND category = 'Sécurité'
On demande tous les articles publiés de la catégorie « Sécurité ».
Un petit SQL Fiddle pour illustrer tout ça.
Bon c'est très basique comme exemple, mais c'est vraiment pour faire simple.
Imaginons maintenant que le client demande la page ' OR published = 0 #
, à la place de «Sécurité
», la requête devient donc :
SELECT * FROM ARTICLES WHERE published = 1 AND category = '' OR published = 0 #'
Le client recevra alors tous les articles non publiés, et a donc récupéré des données privées. Il est évidemment possible de faire bien plus, comme nous le verrons par la suite.
Voici le lien SQL Fiddle.
À travers cet exemple, nous voyons, qu'il peut être simple d'effectuer une injection SQL pour obtenir des informations cachées.
Malheureusement, cela peut être aussi simple que ça, et les informations récupérés peuvent être vraiment critiques.
Mais les injections SQL permettent bien plus que de récupérer des informations d'une base de donnée.
Juste afficher les articles cachés, c'est pas ce que l'on voit aux infos! Quelles sont donc toutes les possibilités qu'offrent les injections SQL ?
À travers une injection SQL il est possible de :
C'est si facile que ça ?
En réalité, il existe plusieurs type d'injection SQL avec un degrés de complexité différent :
Il s'agit des injections les plus simples, les plus faciles à détecter, il n'y a aucune ou peu de protections. L'exemple le plus courant est le formulaire d'authentification.
Pour cette technique et celles qui en découlent (error based et union based), les erreurs SQL s'affichent bien souvent, facilitant grandement l'exploitation de la vulnérabilité.
Cette technique se base sur le fait que les erreurs SQL sont affichées sur la page et en partie interprétées. Il est donc possible de faire afficher les informations souhaitées dans les erreurs.
Par exemple la requête :
SELECT exp(~(select*from(select database())x));
va produire l'erreur :
Data truncation: DOUBLE value is out of range in 'exp(~((select 'articles' from dual)))'
Si nous remplaçons database()
par user()
nous aurons le nom de l'utilisateur courant.
Sur cette technique, on utilise le mot clé UNION
du langage SQL pour adjoindre une autre requête.
Si nous reprenons l'exemple de l'utilisateur qui souhaite récupérer la liste des articles de la catégorie « Sécurité » :
SELECT * FROM ARTICLES WHERE published = 1 AND category = 'Sécurité' UNION SELECT 1,username,password,4,5 FROM USERS
Avec cette requête, l'utilisateur aura la liste d'article de la catégorie « Sécurité » mais aussi la liste des comptes utilisateurs et leur mot de passe.
Un petit exemple avec SQL Filddle).
Comme son nom l'indique, il faudra obtenir les informations à aveugle, ou plutôt à tâtons.
L'idée ici est de trouver un moyen de dialoguer avec la base de donnée de manière binaire : vrai ou faux, car aucun message d'erreur ne s'affichera.
Voici un petit exemple, dans l'URL d'un site j'observe un paramètre numérique « id » qui change lorsque je vais de page en page :
index.php?id=97
Si j'essaie les techniques précédentes, aucun message ni aucune erreur n'apparaît.
L'absence de protection permet tout de même d'effectuer une injection SQL.
Il suffit par exemple de remplace dans l'URL 97
par if([...],97,-1)
avec [...]
à remplacer par une condition à tester (exemple : savoir si un utilisateur s'appelle « admin »).
Si c'est vrai alors la page 97
s'affichera, sinon ce sera la page -1
qui provoquera une erreur (souvent 404).
Il est alors possible de scripter le processus pour obtenir toutes les informations souhaitées.
Un petit SQL Fiddle pour illustrer ça (il suffit d'échanger « false » avec « true » pour faire changer le résultat).
Cette technique découle de blind based, sauf qu'il peut être difficile suivant les protections d'effectuer cette dernière.
On va donc utiliser le temps et non pas les pages renvoyées pour mettre en place un dialogue avec la base de donnée.
En effet, ici on utilise les fonctions permettant d'attendre pour que la requête prenne plus de temps si c'est vrai.
Exemple de fonctions (dépend de la base de donnée) : SLEEP(), BENCHMARK(), WAIT FOR DELAY, etc...
Si nous reprenons l'exemple précédent, dans l'URL je peux taper : 97 OR IF([...],SLEEP(5),0)
avec [...]
à remplacer par une condition à tester.
Encore un SQL Fiddle, il suffit de changer « false » avec « true » et la valeur importante à regarder est « Execution Time ».
C'est un peu vague tout ça. T'aurais pas des exemple pour illustrer un peu mieux ?
Il y a quelques cas d'injection SQL courantes que nous pouvons analyser.
Nous allons utiliser le langage PHP et SQL pour illustrer tout ça, car c'est le duo le plus utilisé.
Lorsque nous arrivons sur le formulaire de login, il y a deux types de requêtes SQL possibles.
La première insère l'username et le mot de passe dans la requête de cette façon :
$query = "SELECT * FROM USERS WHERE username = '".$_GET['username']."' AND password = '".$_GET['password']."' LIMIT 1;";
Si une ligne est retournée, c'est que les identifiants sont bons.
Il est facile de contourner cette authentification avec une injection SQL simple.
Si un utilisateur « admin » existe, il sera possible de mettre dans le champ nom d'utilisateur : « admin'# » et n'importe quoi dans le champ mot de passe.
La requête SQL devient donc :
SELECT * FROM USERS WHERE username = 'admin'#' AND password = 'nimportequoi' LIMIT 1;
La requête va chercher l'utilisateur admin mais le reste est commenté. Donc l'utilisateur admin est retourné, et l'utilisateur est authentifié.
Et le SQL Fiddle qui va bien.
L'autre requête n'insère que le nom d'utilisateur dans le requête et récupère le mot de passe pour ensuite le comparer avec celui fournit par l'utilisateur. Si c'est le même, on authentifie l'utilisateur.
$query = "SELECT username,password FROM USERS WHERE username = '".$_GET['username']."' LIMIT 1;";
C'est un peu plus complexe ici car il faut connaître la fonction de hash utilisé (peut être déterminé après quelques tests), et faire en sorte que le seul résultat retourné soit notre mot de passe hashé.
Exemple d'injection de type union based : dans le champ nom d'utilisateur ' UNION SELECT 'admin','e10adc3949ba59abbe56e057f20f883e' #
avec e10adc3949ba59abbe56e057f20f883e
le hash de ce que l'on a passé dans le champ mot de passe : « 123456 ».
La requête donnera donc :
SELECT username,password FROM USERS WHERE username = '' UNION SELECT 'admin','e10adc3949ba59abbe56e057f20f883e' #' LIMIT 1;
Ce qui retournera le hash du mot de passe que l'on a passé en paramètre. L'utilisateur sera donc authentifié en tant que « admin ».
Le SQL Fiddle.
Beaucoup de sites et blogs passent l'ID du post/article en paramètre dans l'URL.
Les URL sont du type : index.php?id=X
avec X étant l'ID de l'article.
La requête qui récupère l'article est :
SELECT * FROM ARTICLES WHERE id = 8;
Si les erreurs s'affichent, on pourra utiliser la technique union based, sinon ce sera du blind based.
La difficulté pour une union based sera de trouver le nombre de champs dans la table « ARTICLES ».
Voici un exemple de requête pour union based :
SELECT * FROM ARTICLES WHERE id = 3 UNION SELECT 1,username,password,4,5 FROM USERS WHERE username = 'admin' LIMIT 1 OFFSET 1;
Le SQL Fiddle qui va bien.
Pour la partie blind based la requête sera du genre :
SELECT * FROM ARTICLES WHERE id = if((SELECT substr(password,1,1)='5' FROM USERS WHERE username = 'admin'),3,-1)
Si la première lettre du hash du mot de passe de l'admin est « 5 » alors on affiche l'article 3 sinon l'article -1 (qui n'existe pas).
Un fois que l'on a la première lettre, il suffit de passer à la deuxième et ainsi de suite.
C'est gourmand en requête mais avec un petit script ça se fait rapidement.
Et le SQL Fiddle.
Quasiment tous les sites aujourd'hui ont un formulaire de recherche.
Il s'agit bien souvent d'un point d'injection. Voyons voir pourquoi.
La requête SQL est du type :
SELECT * FROM ARTICLES WHERE published = 1 AND content LIKE '%motclé%'
Voici un exemple d'injection :
' AND 0 UNION SELECT 1,username,password,4,5 FROM USERS WHERE username LIKE 'admin
.
Ce qui donne la requête SQL :
SELECT * FROM ARTICLES WHERE published = 1 AND content LIKE '%' AND 0 UNION SELECT 1,username,password,4,5 FROM USERS WHERE username LIKE 'admin%'
Et le SQL Fiddle qui va bien.
Comme nous l'avons vu, il peut y avoir plusieurs points d'injection sur un seul site web.
Il est donc important de vérifier chaque point d'entrée de l'utilisateur pour s'assurer qu'il n'y a pas de vulnérabilités, et c'est valable pour toutes les failles.
C'est bien de savoir ce qu'est une injection SQL mais comment les détecter ?
Le premier pas vers la détection d'une vulnérabilité quelle qu'elle soit, est d'abord de trouver le point d'injection.
C'est tout un art qui sort du cadre de cet article.
Toutefois, sachez qu'il est possible de trouver des points d'injection pour les injections SQL un peu partout (URL, formulaires, cookies, Ajax, etc...).
Ensuite il est nécessaire de faire de nombreux tests pour savoir s'il s'agit d'un point d'injection viable ou non (il est possible de tomber sur une injection SQL et de ne pas réussir à l'exploiter).
Voici un exemple de tests courants :
'
: le single quote permet de lever bien souvent une erreur"
: le double quote dans certain cas remplace le single quote1' or '1'='1
: un exemple un peu plus poussé, très utilisé dans les formulaires de loginadmin'--
: un autre exemple pour les formulaires de login. Le commentaire de fin peu être remplacé par « # » ou « /* » suivant les cas1--
: encore une fois le commentaire de fin peu être remplacé par « # » ou « /* » suivant les cas1+1
: utilisation de l'addition pour voir si c'est interprété%27
: le single quote en URL encode%22
: le double quote en URL encodeIl existe également de nombreux outils automatisés, permettant la détection et l'exploitation d'injections SQL
Voici les plus connus :
Un autre outil un peu plus « exotique » permet de détecter des points d'injections voir des failles d'injection SQL : Google.
En effet depuis de nombreuses année maintenant les Google Dorks servent de plus en plus la sécurité.
Mais je vais pas m'étendre sur le sujet qui est très bien documenté déjà, et très orienté Black Hat.
Pour éviter les injections SQL il y a plusieurs techniques complémentaires.
Chacune des méthodes employées renforcera la sécurité de votre application.
Voici la liste des points à vérifier :
Pour rappel, la criticité, est le facteur de la probabilité par la gravité :
criticité = probabilité x gravité
La probabilité dépend de plusieurs facteurs comme le type de site (ecommerce, blog, etc...), la réputation (beaucoup de visite), le type d'outils utilisés (WordPress, Magento, etc...).
Par contre la gravité dépend elle des barrières rencontrés qui limiteront la portée de la vulnérabilité (cf partie précédente) comme la dissimulations des messages d'erreurs, l'utilisation de requêtes préparées, etc...
Beaucoup d'exploitation d'injection SQL se font aujourd'hui par des robots qui parcours les liens trouvés sur les moteurs de recherches.
Pour chaque lien ils cherchent des points d'injection et testent quelques valeurs pour savoir s'il y a des failles.
Les sites utilisant des outils grands publics comme des CMS (Drupal, WordPress, Joomla, Magento, etc...) sont très recherchés par les robots qui connaissent déjà les failles.
Voici quelques attaques célèbres de ces dernières années.
Ce n'est qu'une goutte d'eau par rapport à l'océan de site web qui sont mis à nu.
Il ne s'agit pas de faire l'apologie de ces attaques, de faire peur, mais bien de faire prendre conscience de l'importance de s'en protéger !
En rentrant leurs informations, les clients font implicitement confiance à l'entreprise pour sécuriser ces données.
Chaque entreprise doit donc s'intéresser à la problèmatique de la sécurité de ces informations pour le bien de tous.
Depuis l'entrée en vigueur de la RGPD le 25 mai 2018, les entreprises victimes d'une fuite de donnée ont plusieurs obligations.
Par exemple, la déclaration de fuite de donnée à la CNIL doit être faites dans un délai de 72h maximum.
Il est nécessaire de prévenir les clients également.
Sans compter qu'une amande peut être appliquée à l'entreprise.
Bref vaut mieux que ça arrive !
Si jamais ça doit vous arriver, faites vous aider.
La CNIL pourra vous conseiller !
Il y a une manière de communiquer, d'analyser et de corriger les failles.
Les injections SQL sont très courantes, répandues, dangereuses et pourtant de nombreux développeurs ignorent jusqu'à leur existence.
Une attaque de ce type peut être très coûteuse pour une entreprise, bien plus que la mise en place de protections adéquates.
Comme nous l'avons vu, il est possible d'éviter tout cela facilement.
J'espère que cet article vous a plu. N'hésitez pas à le partager à votre famille, vos amis, et aux sites vulnérables.
Trois articles intéressants de l'OWASP:
Comment se protéger des injections SQL d'après php.net : http://php.net/manual/fr/security.database.sql-injection.php
Les requêtes préparées et les procédures stockées par php.net : http://php.net/manual/fr/pdo.prepared-statements.php
Une liste tenue à jour des attaques par injection SQL : http://codecurmudgeon.com/wp/sql-injection-hall-of-shame/
Un wiki sympa : https://sqlwiki.netspi.com
Un autre article complet détaillé avec les versions de bases de données : https://www.netsparker.com/blog/web-security/sql-injection-cheat-sheet/
Article intéressant concernant les avantages des procédures stockées : http://www.dbnewz.com/2010/12/08/pour-ou-contre-les-procedures-fonctions-stockees/
Article autour des avantages des requêtes préparées : http://www.experience-developpement.fr/php-pdo-requetes-preparees/
Article très sympa à lire décrivant de nombreux scénarii analysés : http://christianelagace.com/wordpress/comment-les-hackers-reussissent-les-injections-sql/
Ces deux articles concernent le contournement des protections :