Souvent, les sites Web semblent exister principalement pour mettre quelque chose dans une base de données afin de le retirer plus tard. Alors que d'autres méthodes de base de données, telles que NoSQL, ont gagné en popularité au cours des dernières années, les données de nombreux sites Web résident toujours dans la base de données SQL traditionnelle. Ces données consistent souvent en des informations personnelles précieuses telles que des numéros de carte de crédit et d'autres informations personnelles présentant un intérêt pour les voleurs d'identité et les criminels. Les pirates cherchent donc toujours à obtenir ces données. L'une des cibles les plus courantes de ces attaques est la base de données SQL qui se cache derrière de nombreuses applications Web via un processus d'injection SQL..
Une attaque par injection fonctionne en obligeant l'application à transmettre une entrée non approuvée à l'interprète. Récemment, 40 000 enregistrements de clients provenant de Bell Canada sont le résultat d’une attaque par injection SQL. À la fin de 2013, des pirates informatiques ont volé plus de 100 000 dollars à un fournisseur de services Internet basé en Californie en utilisant l'injection SQL.
Le projet de sécurité des applications Web ouvertes (OWASP) a choisi l'attaque par injection comme principal risque de sécurité des applications dans le top 10 en 2013, en fonction de sa prévalence et du risque pour les systèmes Web attaqués. Malheureusement, il occupait également la première place dans le précédent rapport de 2010. Alors, qu'est-ce qu'une attaque par injection SQL? Dans ce tutoriel, je vais expliquer leur fonctionnement et ce que vous pouvez faire pour protéger votre application de ces attaques..
Tout système interprété derrière un serveur Web peut être la cible d’une attaque par injection. Les cibles les plus courantes sont les serveurs de base de données SQL utilisés par de nombreux sites Web. L'injection SQL ne résulte pas directement de faiblesses dans la base de données, mais utilise des ouvertures dans l'application pour permettre à l'attaquant d'exécuter des instructions de son choix sur le serveur. Une attaque par injection SQL amène la base de données à partager plus d'informations que ce que l'application est conçue pour fournir. Regardons un appel de base de données SQL que vous pourriez écrire dans ASP.NET.
SqlCommand command = new SqlCommand ("SELECT * FROM userdata WHERE UserId =" + id); SqlDataReader reader = command.ExecuteReader ();
Quel est le problème avec ce code? Peut-être rien. Le problème est que identifiant
chaîne que nous utilisons. D'où vient cette valeur? Si nous le générons en interne ou à partir d'une source approuvée, ce code peut fonctionner sans problème. Cependant, si nous obtenons la valeur d’un utilisateur et l’utilisons sans modification, nous nous sommes ouverts à l’injection SQL..
Prenons un cas courant où nous obtenons le paramètre à rechercher dans le cadre de l'URL. Prenez l'URL http://www.example.com/user/details?id=123
. Dans ASP.NET en utilisant C #, nous pouvons obtenir cette valeur passée en utilisant ce code:
string id = Request.QueryString ["id"];
Ce code combiné à l'appel ci-dessus nous laisse exposés à une attaque. Si l'utilisateur transmet un identifiant tel que 123, comme prévu, tout fonctionne correctement. Cependant, rien de ce que nous avons fait ici ne garantit que c'est le cas. Disons que l'attaquant tente d'accéder à l'URL http://www.example.com/user/details?id=0; SELECT * FROM userdata
.
Au lieu de simplement transmettre une valeur comme prévu, nous avons donné une valeur, puis ajouté un point-virgule, qui termine une instruction SQL. Il ajoute ensuite une deuxième instruction SQL que l'attaquant voudrait exécuter. Dans ce cas, tous les enregistrements de la table userdata seront renvoyés. Selon le reste du code de notre page, cela pourrait renvoyer une erreur ou peut-être afficher tous les enregistrements de la base de données à l'attaquant. Même une erreur peut être utilisée avec des requêtes soigneusement construites pour créer une vue de la base de données. Pire encore, imaginez que l'attaquant accède à une URL de http://www.example.com/user/details?id=0; DROP TABLE userdata
. Maintenant, tous vos utilisateurs sont perdus.
Dans cet exemple, l'attaquant peut exécuter le code de son choix sur le serveur de base de données. Si le compte sous lequel les appels de base de données sont exécutés en a le plein contrôle, un scénario trop courant, puis supprimer des tables et supprimer des enregistrements constitue un moyen simple de supprimer un site. Même si l’attaquant ne peut que lire et écrire des données dans la base de données, il n’est qu’une question de patience et de prudence..
Prenez une simple base de données nommée "Produits" composée de trois colonnes. La première colonne contient l'identifiant du produit, la seconde le nom du produit et la troisième le prix du produit. Pour notre requête normale, nous allons essayer de trouver tous les produits contenant un widget dans le nom. Le SQL pour faire cela dans le même modèle que nous avons montré ressemblerait à ceci:
string sql = “SELECT * FROM Produits WHERE nom de produit LIKE '%” + moteur de recherche + “%'”;
Un site Web typique pour accéder à cela ressemblerait http://www.example.com/product?search=widget
. Ici, la page Web va simplement parcourir chaque enregistrement retourné et l'afficher à l'écran. Dans ce cas, nous voyons notre produit de widget.
| productid | nom du produit | prix | | ----------- | 1 | Widget | 100.00 |
Changeons notre requête en http://www.example.com/product?search=widget 'OR 1 = 1;--
et exécuter la même requête. voir quelque chose de plus. En fait, nous verrons tous les enregistrements de la table.
| productid | nom du produit | prix | | --- | 1 | Widget | 100.00 | | 2 | Thingy | 50.00 | | 3 | Boxy | 125.00 |
Nous avons trompé l'interprète pour qu'il exécute le code SQL de notre choix. Le résultat SQL exécuté sera:
SELECT * FROM Produits WHERE nom de produit LIKE '% widget' OU 1 = 1; -% '
Le résultat sera deux déclarations:
SELECT * FROM Produits WHERE nom de produit LIKE '% widget' OU 1 = 1; -% '
En ajoutant le 1 = 1
, ce qui est toujours vrai, la clause where sera vraie pour chaque ligne de la table et la requête résultante renverra chaque ligne. le --
au début de la deuxième instruction transforme le reste de l'instruction SQL en un commentaire qui empêche le message d'erreur que nous verrions sinon.
Les modifications apportées à l'affichage ne peuvent empêcher cette affirmation. Un attaquant patient peut utiliser même rien de plus que le fait qu’une valeur soit renvoyée ou non et des requêtes soigneusement construites pour mapper lentement votre base de données et éventuellement récupérer des données même s’ils ne voient qu’un message d’erreur. Il existe des outils tels que sqlmap pour automatiser le processus.
Vous empêchez l'injection SQL en empêchant les entrées non fiables d'accéder à la base de données SQL ou à un autre interpréteur. Toute entrée extérieure au système doit être considérée comme non fiable. Même les données provenant d'autres systèmes partenaires doivent être considérées comme non fiables, car vous n'avez aucun moyen de garantir que l'autre système ne souffre pas de problèmes de sécurité qui pourraient permettre l'insertion de données arbitraires puis transmises à votre application.
Pour revenir à notre exemple précédent, si nous savons que le paramètre id doit toujours être un entier, nous pouvons essayer de le convertir en entier et afficher une erreur en cas d'échec. Une application ASP.NET MVC le ferait avec un code similaire à celui-ci:
int quantité; if (! int.TryParse (Request.QueryString ["quantité"], nombre de pièces))) return RedirectToAction ("Invalid");
Cela tentera de convertir la chaîne en un entier. Si la conversion échoue, le code est redirigé vers une action qui affichera un message non valide..
Cela peut être évité si nous recherchons un paramètre entier. Cela ne nous aiderait pas si nous attendions un texte comme dans la recherche de produit précédente. La technique préférée dans ce cas consiste à utiliser des expressions régulières ou des remplacements de chaîne pour n'autoriser que les caractères nécessaires dans la valeur transmise..
Pour ce faire, vous pouvez ajouter à la liste blanche, au processus de suppression des caractères autres qu'un ensemble spécifié ou à la liste noire, le processus de suppression d'un membre de l'ensemble spécifié de la chaîne. La liste blanche est plus fiable puisque vous ne spécifiez que les caractères autorisés. Pour supprimer autre chose que des lettres et des chiffres dans une chaîne, nous pouvons utiliser un code tel que:
Regex regEx = new Regex ("[^ a-zA-Z0-9 -]"); string filterString = regEx (originalString, "");
Vous devrez évaluer chaque entrée, respectivement. Certaines requêtes de base de données peuvent nécessiter une attention plus spécialisée. Prenez des caractères qui peuvent avoir un sens dans une commande SQL mais peuvent également être un caractère valide dans un appel de base de données. Par exemple, le caractère guillemet simple 'est utilisé pour démarrer et terminer une chaîne en SQL, mais peut également faire partie du nom d'une personne, tel que O'Conner. Dans ce cas, remplacer le devis simple '
avec des citations simples consécutives "
peut éliminer le problème.
Les procédures stockées sont souvent considérées comme la solution à ce problème et peuvent faire partie de la solution. Cependant, une procédure stockée mal écrite ne vous sauvera pas. Prenez cette procédure stockée qui inclut un code similaire à ce que nous avons déjà vu pour créer une requête:
ALTER PROCEDURE [dbo]. [SearchProducts] @searchterm VARCHAR (50) = "COMME COMMENCER DECLARER @query VARCHAR (100) SET @query =" SELECT * FROM produits WHERE nom de produit LIKE "% '+ @searchterm +'%"; EXEC (@query) END
La concaténation de chaîne est ici le problème. Essayons une tentative simple dans laquelle nous essayons de définir une condition toujours vraie pour afficher toutes les lignes de la table et transmettre le même 'widget' OU 1 = 1; - "à la requête que nous avons vue précédemment. Le résultat est également identique. :
| productid | nom du produit | prix | | - | 1 | Widget | 100.00 | | 2 | Thingy | 50.00 | | 3 | Boxy | 125.00 |
Si des données non fiables sont transmises, nous obtenons le même résultat que si l'appel était créé dans notre code. Que la concaténation de chaîne ait lieu dans une procédure stockée au lieu de dans notre code ne fournit aucune protection.
La prochaine pièce du puzzle à protéger contre les attaques par injection consiste à utiliser le paramétrage. Construire des requêtes SQL en concaténant des chaînes puis en transmettant le code fini ne donne à la base de données aucune idée de la partie de la chaîne qui est un paramètre et de celle de la commande. Nous pouvons vous aider à vous protéger contre les attaques en créant des appels SQL de manière à garder les déclarations et les valeurs distinctes..
Nous pouvons réécrire la procédure stockée présentée précédemment pour utiliser des paramètres et générer un appel plus sûr. Au lieu de concaténer le %
caractères qui représentent des caractères génériques, nous allons créer une nouvelle chaîne en ajoutant ces caractères, puis passer cette nouvelle chaîne en tant que paramètre à l'instruction SQL. La nouvelle procédure stockée ressemble à ceci:
ALTER PROCEDURE [dbo]. [SearchProductsFixed] @searchterm NVARCHAR (50) = "AS BEGIN DECLARE @query NVARCHAR (100) DECLARE @msearch NVARCHAR (55) SET @msearch = '%' + @searchterm + '%' SET 'requête = 'SELECT * FROM Produits WHERE nom_produit LIKE @search' EXEC sp_executesql @query, N '@ search VARCHAR (55)', @msearch END
L'exécution de cette procédure stockée avec uniquement le mot widget fonctionne comme prévu..
| productid | nom du produit | prix | ---- | 1 | Widget | 100.00
Et si nous passons avec notre widget de paramètre "OR 1 = 1; - aucun résultat ne sera renvoyé indiquant que nous ne sommes plus vulnérables à cette attaque..
Le paramétrage ne nécessite pas de procédures stockées. Vous pouvez également en tirer parti avec des requêtes construites dans le code. Voici un court segment en C # pour vous connecter et exécuter une requête contre Microsoft SQL Server à l'aide d'un paramètre.
const string sql = "SELECT * FROM Produits WHERE nom du produit LIKE @CategoryID"; var connString = WebConfigurationManager.ConnectionStrings ["ProductDatabase"]. ConnectionString; using (var conn = new SqlConnection (connString)) var commande = new SqlCommand (sql, conn); command.Parameters.Add ("@ searchterm", SqlDbType.NVarChar) .Value = string.Format ("% 0%", searchTerm); command.Connection.Open (); SqlDataReader reader = command.ExecuteReader (); while (reader.NextResult ()) // Le code à exécuter sur chaque ligne va ici
Jusqu'à présent, j'ai montré comment atténuer les attaques de bases de données. Une autre couche de défense importante, minimise les dommages causés au cas où l'attaquant dépasse les autres défenses. La notion de moindre privilège donne à un module de code qui, dans ce cas, notre base de données appelle, doit uniquement avoir accès aux informations et ressources nécessaires à ses besoins..
Lorsqu'une commande de base de données est exécutée, elle est exécutée sous les droits d'un compte d'utilisateur. Nous gagnons en sécurité en donnant au compte les appels à la base de données exécutés uniquement avec le droit d'effectuer les tâches normalement requises. Si les appels d'une base de données doivent uniquement lire des données d'une table, accordez uniquement au compte les droits de sélection sur la table et non les insérer ou les supprimer. S'il y a des tables spécifiques qu'il faut mettre à jour, par exemple une table de commandes, puis insérez-la et mettez-la à jour, mais pas une autre table telle qu'une table contenant des informations sur l'utilisateur..
Les annulations de commandes peuvent être traitées via un compte séparé utilisé uniquement sur la page effectuant cette tâche. Cela empêcherait la suppression d'un ordre de la table ailleurs plus difficile. Utilisation d'un compte de base de données distinct pour les fonctions administratives du site, avec les droits nécessaires plus importants que ceux que le public utilise peuvent faire beaucoup pour éviter tout dommage qu'un utilisateur trouve une attaque par injection ouverte..
Cela n'empêchera pas toutes les attaques. Cela ne ferait rien pour empêcher de renvoyer des résultats supplémentaires tels que l'exemple précédent affichant l'intégralité du contenu d'une table. Cela empêcherait les attaques de mettre à jour ou de supprimer des données.
L'injection SQL est l'attaque la plus dangereuse, surtout si l'on tient compte de la vulnérabilité des sites Web et du potentiel de ce type d'attaque de causer de nombreux dégâts. Ici, j'ai décrit les attaques par injection SQL et démontré les dégâts que l'on peut causer. Heureusement, il n’est pas si difficile de protéger vos projets Web de cette vulnérabilité en suivant quelques règles simples..
Ne faites jamais confiance aux données extérieures introduites dans votre application. Il doit être validé par rapport à une liste blanche d’entrées valides avant d’être traité ultérieurement. Cela peut signifier de s'assurer qu'un paramètre entier est réellement un entier ou qu'une date est une valeur de date valide. Validez également le texte pour n’inclure que les caractères dont le paramètre aurait besoin. Pour une recherche de texte, vous pouvez souvent n’autoriser que les lettres et les chiffres et filtrer les signes de ponctuation qui pourraient poser problème, comme le signe égal ou le point-virgule..
Utilisez le paramétrage et évitez la concaténation de chaînes lors de la création d'appels SQL. Les procédures stockées ne sont pas une panacée, car elles peuvent également être vulnérables si une simple concaténation de chaînes est utilisée. Le paramétrage évite de nombreux problèmes de concaténation de chaînes.
Le code accédant à la base de données doit être exécuté avec le minimum de privilèges nécessaires pour effectuer les tâches requises. Dans peu de cas, les appels de base de données utilisés par l'application Web doivent modifier la structure de la base de données, par exemple supprimer ou modifier des tables. Vous pouvez ajouter une couche de protection supplémentaire en exécutant différentes parties du site Web sous différents comptes. Le compte de base de données utilisé pour les actions utilisateur normales n'a probablement aucune raison de modifier la table contenant les rôles ou les droits des utilisateurs. L'exécution des parties administratives du site sous un compte plus privilégié et la section de l'utilisateur final sous un compte moins privilégié peuvent grandement contribuer à réduire les risques de perte de code qui pourrait en générer d'autres..