Injections SQL

19 minutes
sql injection injection sql

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.

Schéma du dialogue client-serveur

Maintenant, en s'appuyant sur l'image précédente, prenons un exemple un peu plus poussé pour illustrer une injection SQL :

  1. le client interroge le serveur pour obtenir tous les articles de la catégorie « Sécurité » du site web https://www.sebastien-gandossi.fr
  2. le serveur du site web envoie une requête à la base de donnée pour récupérer tous les articles de la catégorie « Sécurité »
  3. la base de donnée répond avec la liste des articles, que le serveur utilise pour créer la page web
  4. le serveur retourne la page web au client

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 :

  • récupérer des données (comptes utilisateurs, cartes bancaires, etc...). On parle alors souvent de vols de données dans l'actualité. C'est l'utilisation la plus courante
  • contournement d'authentification : on s'authentifie sur une application sans avoir des identifiants valides
  • ajout et/ou modification des données : par exemple pour ajouter un compte administrateur, ou modification d'un compte existant pour ajouter des privilèges
  • supprimer des informations, ou vider la base de donnée : si une personne a une dent contre une entreprise, ou juste pour embêter son monde. C'est l'utilisation la moins courante.
  • exécution de commande système : ça permet d'avoir plus d'informations sur la machine, lancer des attaques à distance, installer des programmes, etc...
  • escalade de privilège pour augmenter le niveau d'accès d'un utilisateur à la machine, et au final avoir l'accès total : administrateur ou root
  • déni de service (DoS) : sature le serveur de base de donnée ce qui rend le site très lent et donc inaccessible
  • lire un fichier : utile pour voir le code source d'une application

C'est si facile que ça ?

En réalité, il existe plusieurs type d'injection SQL avec un degrés de complexité différent :

Les différents types d'injection SQL

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 quote
  • 1' or '1'='1 : un exemple un peu plus poussé, très utilisé dans les formulaires de login
  • admin'-- : un autre exemple pour les formulaires de login. Le commentaire de fin peu être remplacé par « # » ou « /* » suivant les cas
  • 1-- : encore une fois le commentaire de fin peu être remplacé par « # » ou « /* » suivant les cas
  • 1+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 encode
    C'est en quelque sorte la « base » qu'il est fortement conseillé de compléter et d'améliorer en fonction de la situation.

Il existe également de nombreux outils automatisés, permettant la détection et l'exploitation d'injections SQL
Voici les plus connus :

  • SQLMap : outil le plus connu d'automatisation de détection et d'exploitation d'injection SQL. Il est beaucoup utilisé en CTF
  • Nikto : outil d'audit d'application web. Il trouve tout seul les points d'injection et les failles associés (dont les injections SQL)
  • Nessus : scanner de vulnérabilité propriétaire très connu, il trouvé également les points d'injection et les failles associés (dont les injections SQL)
    Et bien d'autres (wapiti, w3af, etc...)

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 :

  • les informations entrées par l'utilisateur doivent toujours être vérifiées. C'est valable pour tous types d'applications et de vulnérabilités. En PHP par exemple, l'utilisation de la fonction « mysqli_real_escape_string() » est incontournable.
  • utiliser les procédures stockées : ce sont des routines effectuant toujours les mêmes actions. Elles constituent une aide pour lutter contre les injections SQL en plus du gain de performance apporté
  • requêtes préparées : la requête sera analysée, compilée et optimisée avant d'insérer les paramètres. Elles apportent souvent un gain de performance aussi. C'est également incontournable
  • expressions régulières : ça peut être utile de filtrer les informations entrées par l'utilisateur. Par exemple, si y a les mots clés « union, select » on renvoie vers une page par défaut (erreur ou autre)
  • gérer les droits de connexion à la base de donnée : mettre uniquement les droits nécessaires, voir même créer plusieurs profils en fonction des besoins
  • les messages d'erreur : il est important de ne jamais afficher les messages d'erreurs sur l'environnement de production. Il vaut mieux les loggers dans des fichiers
  • droits des fichiers : il faut vérifier les droits des fichiers de l'application, pour éviter qu'il ne soit possible de les lire à l'aide d'une injection SQL
  • logs : en cas d'attaque, les fichiers de logs (serveur et applicatif) constituent votre meilleurs allié pour trouver rapidement le point d'injection et prendre les mesures adéquates. Il est évident qu'il faut les sauvegarder régulièrement (par exemple tous les jours) pour être sûr qu'ils ne soient pas effacés
  • accès au serveur de base de donnée : vérifiez également les applications qui accèdent à la base de donnée et leurs sécurités respectives (exemple : PhpMyAdmin, accès au serveur de base de donnée depuis l'extérieur, etc...)
  • contrôle plus haut niveau : il est possible de faire des vérifications à plus haut niveau. Par exemple, il est possible d'utiliser « Apache mod_security », ou encore un IDS comme « snort »
  • séparer les environnements : si possible, il est conseillé de séparer la machine hébergeant la base de donnée de la machine hébergeant le site web. C'est plus facile à maintenir, à superviser et si la machine du site web est piratée, celle de la base de donnée ne l'est pas

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 !

  • les serveurs web de Lenovo tombent :1 million d'utilisateurs exposés (lien)
  • Epic Games éditeur du jeux Fortnite a été victime d'une faille SQLi et XSS exposant les informations de tous les joueurs (lien)

  • un chercheur en sécurité trouve une faille dans les API de Steam donnant accès aux Clés CD de tous les jeux (lien)
  • une vulnérabilité SQL et 3 XSS corrigées dans Joomla : (lien)

  • Equifax aggrège et traite les données de consommation de million de personnes à travers le monde pour établir la solvabilité et la capacité de remboursement d'un crédit d'un personne ou d'une entreprise. Equifax s'est autoproclamé spécialiste en sécurité informatique. Mais l'entreprise a subit une attaque exposant les informations de plus de 143 millions de personnes. Ça a fait énormément de bruit cette affaire (lien)
  • une année bien sombre pour Wordpress. Plusieurs failles SQL dans des plugins très utilisés et une dans le CMS (lien lien lien)

  • le forum du jeu vidéo Dota2 tombe, 1.9 millions de comptes exposés (lien)
  • fuite d'information sur le forum Ubuntu de Canonical. Près de 2 millions de comptes sont compromis (lien)

  • vol de donnée sur le serveur de la British telecommunications company, 400 000 de comptes volés (lien)
  • le site controversé d'adultère Ashley Madison piraté (désolé pour le jeu de mots) : 37 millions de comptes concernés (lien)

  • vol de données au Wall Street Journal (lien)
  • un groupe Russe utilise un Botnet pour voler les mots de passe de nombreux sites web, l'estimation est de 400 000 sites compromis (lien)

  • intrusion sur les serveurs de la NASA, U.S. Army et des agences gouvernementales américaines (lien)
  • 50 millions de comptes utilisateurs d'Evernote réinitialisés (lien)

  • vol de données pour les 6.5 millions de comptes LinkedIn (lien)
  • 450 000 comptes Yahoo! dans la nature (lien)

  • vol de 75 millions de comptes du Playstation Network (lien)
  • le site MySQL.com tombe (lien)

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 :

Blog Comments powered by Disqus.

Article précédent Article suivant