Bien démarrer avec la paramétrisation des paramètres de requête
Mary Gathoni
24 octobre 2023
0 minutes de lectureLorsque les applications Web utilisent des données fournies par les utilisateurs, elles sont exposées à un risque d’attaque par injection SQL. L’injection SQL est une technique qui consiste à modifier une instruction SQL en manipulant les données saisies par l’utilisateur. Plus précisément, les attaquants envoient du code SQL malveillant à la base de données pour contourner les mesures de sécurité et accéder aux données sans autorisation. Les conséquences peuvent être graves, allant de la fuite d’informations sensibles à la compromission complète du système.
Il existe plusieurs façons de prévenir une attaque par injection SQL, notamment la paramétrisation des requêtes SQL. Celle-ci consiste à séparer le code SQL des valeurs saisies par l’utilisateur à l’aide d’espaces réservés (paramètres), afin que les données saisies soient traitées comme des données et non comme du code SQL. L’essentiel est d’utiliser des instructions préparées avec des requêtes paramétrées : une combinaison particulièrement efficace.
Pour renforcer la sécurité, nous pouvons utiliser des procédures stockées paramétrées. Les procédures stockées sont des instructions SQL précompilées et enregistrées dans la base de données. Nous pouvons accorder le droit d’exécuter la procédure stockée tout en limitant l’accès direct à la table sous-jacente. Pour utiliser une procédure, il suffit de lui transmettre les paramètres attendus depuis notre application.
Dans cet article pratique, nous verrons comment utiliser la paramétrisation des requêtes SQL et les procédures stockées pour prévenir les attaques par injection, ainsi que d’autres mesures de sécurité pour protéger notre code.
Mettre en œuvre la paramétrisation des requêtes
Pour paramétrer une requête, on utilise des espaces réservés dans les requêtes SQL afin de représenter les données fournies par l’utilisateur. Ces marqueurs temporaires sont remplacés par le moteur SQL par les valeurs réelles des variables. Lors de l’exécution de la requête, le moteur SQL associe les variables aux espaces réservés avant de lancer la requête, ce qui garantit que les variables sont traitées comme des données littérales.
Vous pouvez paramétrer les requêtes avec ou sans instructions préparées. La différence entre ces deux approches est que les instructions préparées sont précompilées et peuvent être appelées plusieurs fois avec différents paramètres.
Plus important encore, les instructions préparées permettent d’améliorer la paramétrisation des requêtes, car la base de données garantit que l’instruction est précompilée, ce qui réduit le temps d’exécution. De plus, le plan d’exécution étant déjà créé, les paramètres ne peuvent pas l’influencer : un avantage pour votre posture de sécurité.
Si vous devez appeler une requête plusieurs fois, la base de données n’aura pas besoin de la recompiler : il vous suffira de transmettre les paramètres souhaités à l’instruction préparée. En outre, les paramètres liés réduisent la bande passante, puisque seuls les paramètres sont envoyés au serveur, et non la requête entière.
Les sections suivantes montrent comment mettre en œuvre la paramétrisation des requêtes dans une application Web.
Prérequis
Pour suivre cette démonstration, assurez-vous de disposer des éléments suivants :
Une bonne connaissance de .NET
Une bonne connaissance de l’écriture de requêtes SQL.
Paramétrisation des requêtes sans instructions préparées
Pour les besoins de la démonstration, nous utilisons une application console .NET qui accepte une saisie utilisateur en ligne de commande, puis l’utilise comme l’une des valeurs d’une requête SQL.
Commençons par voir comment paramétrer la requête sans utiliser d’instruction préparée.
Dans le code ci-dessous, récupérez le nom d’utilisateur avec `Console.Readline()`, puis définissez la requête. Au lieu de concaténer le nom d’utilisateur à la requête, utilisez un espace réservé précédé du caractère `@`. Nous utiliserons ensuite la méthode `AddWithValue()` pour lier le nom d’utilisateur à la requête avant son exécution :
En transmettant les valeurs des paramètres après avoir défini la requête, nous veillons à ce que le serveur SQL les interprète littéralement.
Paramétrisation des requêtes avec des instructions préparées
Dans le même scénario, voyons comment modifier la requête de recherche pour utiliser une instruction préparée.
La chaîne de requête reste identique et utilise toujours les espaces réservés `@id` et `@username`. Toutefois, contrairement à l’exemple précédent, nous appelons la méthode `Prepare` sur l’objet `SqlCommand` après avoir défini les paramètres.
Avant d’appeler `Prepare()`, précisez le type de données de chaque paramètre dans l’instruction. Vous devez également définir sa propriété de taille à sa valeur maximale pour éviter une erreur.
Utilisez ensuite la méthode `ExecuteNonQuery` pour exécuter l’instruction `INSERT` avec les paramètres fournis et insérer les données utilisateur dans la table `Users`, comme le montre l’exemple ci-dessous :
Avec les instructions préparées, il suffit d’appeler la méthode `Prepare()` une seule fois. Ensuite, nous pouvons modifier les valeurs des paramètres et exécuter la requête plusieurs fois sans la compiler à nouveau.
Utiliser des procédures stockées
Les instructions préparées nous permettent de réutiliser des requêtes dans notre application. Toutefois, contrairement aux procédures stockées, elles ne peuvent pas être appelées depuis plusieurs applications et ne permettent pas de mettre en œuvre des mesures de contrôle d’accès.
Les procédures stockées sont des requêtes précompilées enregistrées sur le serveur de base de données, que nous pouvons appeler à plusieurs reprises depuis diverses applications. Lorsqu’elles sont correctement mises en œuvre, elles offrent la même protection contre les attaques par injection SQL que les instructions préparées. Nous pouvons contrôler qui est autorisé à exécuter une procédure stockée et même limiter l’accès direct à la table sous-jacente, réduisant ainsi l’impact d’une éventuelle injection SQL.
Comme les instructions préparées, les procédures stockées prennent en charge les requêtes paramétrées et traitent les données saisies par l’utilisateur comme des données plutôt que comme du code SQL. En outre, la base de données assainit automatiquement les paramètres transmis à la procédure. Cela empêche les attaquants d’envoyer du code malveillant à la base de données.
Nous devons utiliser les procédures stockées avec des paramètres de requête, plutôt que d’interpoler directement les valeurs dynamiques dans la requête. En séparant clairement les données saisies par l’utilisateur de l’instruction SQL, nous empêchons le moteur SQL de les interpréter comme du code.
Pour illustrer ce fonctionnement, créons dans notre base de données une procédure stockée qui accepte les valeurs `Id` et `Name` :
Si vous utilisez SQL Server Management Studio, exécutez la procédure dans l’éditeur de requêtes et pensez à remplacer db par le nom de votre base de données.
Voici un exemple d’utilisation de la procédure stockée pour insérer de nouveaux utilisateurs dans la table.
Commencez par utiliser `Console.Readline()` pour demander un nom d’utilisateur en ligne de commande. Établissez ensuite une connexion à la base de données avec la commande `SQLConnection`. Enfin, appelez la procédure stockée `InsertUser` que vous avez créée dans l’exemple précédent.
Pour lier les paramètres à une procédure stockée, utilisez la méthode `Parameters.AddWithValue`. Dans cet exemple, nous fournissons une valeur codée en dur pour le paramètre `@id` et transmettons le nom d’utilisateur saisi par l’utilisateur pour le paramètre `@name`.
Après avoir appelé la procédure stockée et transmis les variables, nous exécutons la requête à l’aide de la méthode `ExecuteNonQuery`.
Mesures de sécurité supplémentaires pour les requêtes de base de données
En plus de paramétrer les requêtes à l’aide d’instructions préparées et de procédures stockées, d’autres mesures permettent de sécuriser les requêtes SQL.
Assainir et valider les données en entrée
Comme indiqué précédemment, les attaquants peuvent exploiter les données saisies pour exécuter du code malveillant dans la base de données. Il est donc essentiel d’assainir et de valider les données en entrée.
L’assainissement permet de supprimer les caractères indésirables (par exemple, `{}`, `'` et `/`) ou le code HTML non sécurisé des données fournies par l’utilisateur. La validation, quant à elle, garantit que ces données respectent le format attendu par la base de données. Par exemple, nous pouvons vérifier la longueur de la saisie et rejeter celles qui sont trop longues. Nous pouvons également imposer un format pour les adresses e-mail et les dates. Cette technique empêche un attaquant de soumettre des valeurs spécialement conçues contenant des instructions SQL.
L’assainissement et la validation des données en entrée permettent de contrôler les valeurs utilisées dans nos requêtes SQL. Toutefois, ces mesures ne sont pas infaillibles : les attaquants peuvent recourir à des techniques comme le double encodage pour contourner nos protections. Par exemple, un attaquant peut encoder une apostrophe (`'`) en `%2527` au lieu de l’encodage habituel `%27` afin de contourner les filtres qui ne décodent qu’une seule fois. Évitez donc de considérer l’assainissement des données en entrée comme une défense contre les injections SQL. Paramétrez plutôt vos requêtes et utilisez l’assainissement comme mesure de sécurité complémentaire.
Encoder les données en sortie
Lorsque vous renvoyez des données saisies par l’utilisateur dans une application Web, encodez-les toujours au préalable. L’encodage en sortie empêche ces données d’être interprétées comme du HTML, du JavaScript ou du SQL.
Utiliser des listes d’autorisation
La paramétrisation des requêtes n’est pas toujours possible. Elle ne prend pas en charge certains éléments des requêtes SQL, comme les noms de tables ou de colonnes. Dans ce cas, mieux vaut valider les données en entrée à l’aide d’une liste d’autorisation. Celle-ci permet de définir des règles strictes qui n’autorisent que certains caractères ou motifs dans les données saisies, afin de rejeter les valeurs malveillantes.
Une liste d’autorisation est plus efficace qu’une liste de refus pour prévenir les attaques par injection SQL. Elle définit explicitement les types de données acceptés, tandis qu’une liste de refus laisse une marge de manœuvre aux attaquants, qui peuvent facilement trouver de nouveaux moyens de la contourner en variant les données saisies. En définitive, une liste d’autorisation vous donne davantage de contrôle sur les valeurs acceptées.
Appliquer le principe du moindre privilège
Pour limiter les conséquences d’une attaque par injection SQL réussie, appliquez toujours le principe du moindre privilège (PoLP). Il restreint les droits d’accès des utilisateurs au strict nécessaire pour accomplir leur travail.
Par exemple, si un compte utilisateur n’a besoin que d’un accès en lecture à une table, le principe du moindre privilège nous conduit à lui accorder uniquement un droit de lecture sur cette table. Si l’utilisateur n’a besoin d’accéder qu’à une partie des données d’une table, nous pouvons créer une vue qui ne contient que ces données, puis donner au compte accès à cette vue plutôt qu’à la table entière.
Si vous utilisez des procédures stockées, limitez les droits d’exécution de votre application aux seules procédures dont elle a besoin. Ainsi, même en cas d’injection SQL, vous limiterez l’accès de l’attaquant et réduirez les conséquences potentielles.
Sécuriser votre code avec Snyk
Réaliser des tests d’injection SQL peut renforcer la protection contre les vulnérabilités de ce type. Les outils automatisés d’analyse statique de la sécurité des applications (SAST), comme Snyk Code, vous aident à détecter les problèmes de sécurité dès le début.
Snyk propose un outil d’analyse des vulnérabilités qui analyse votre code directement dans l’IDE. Vous trouverez également une extension Visual Studio Code dans le gestionnaire d’extensions. Suivez les étapes pour l’installer et la configurer.
Une fois la configuration terminée, cliquez sur le logo Snyk dans la barre latérale pour commencer à l’utiliser. Un menu semblable à celui de l’image ci-dessous devrait s’afficher. Chaque élément de menu comporte une flèche déroulante sur laquelle vous pouvez cliquer pour commencer. Nous utiliserons SÉCURITÉ DU CODE.

Si nous lançons l’analyse de sécurité du code sur une application .NET qui n’utilise pas de requêtes paramétrées, Snyk nous signale que notre projet présente une vulnérabilité d’injection SQL.
Prenons l’exemple de la méthode suivante, qui utilise le formatage de chaînes pour concaténer les données saisies par l’utilisateur à la requête :
Si nous lançons l’analyse de sécurité du code sur le code ci-dessus, Snyk détecte une vulnérabilité, comme le montre la capture d’écran ci-dessous.

Le rapport d’analyse signale une donnée non assainie provenant de l’argument de ligne de commande et utilisée dans la requête SQL, ce qui peut entraîner une vulnérabilité d’injection SQL. Snyk indique le fichier et la ligne concernés. Il propose également des exemples de correctifs possibles afin de vous aider à corriger les vulnérabilités plus rapidement.
Conclusion
La paramétrisation des requêtes est essentielle pour prévenir les attaques par injection SQL. Elle garantit que les données saisies par l’utilisateur sont traitées comme des données et non comme du code exécutable, réduisant ainsi efficacement le risque d’injection SQL malveillante.
Nous avons évoqué les avantages de la paramétrisation des requêtes, avec ou sans instructions préparées. La première option offre une meilleure sécurité, car elle sépare la logique SQL des données saisies par l’utilisateur et élimine le besoin d’échappement ou de nettoyage manuel. Les instructions préparées améliorent également les performances, car les requêtes sont précompilées.
Les procédures stockées constituent une alternative aux instructions préparées. Associées à des paramètres de requête, elles renforcent la sécurité en vous permettant de contrôler les accès.
Pour renforcer encore la sécurité de votre application, vous pouvez intégrer le scanner de vulnérabilités de Snyk à votre workflow de développement. En analysant régulièrement votre base de code, vous pouvez détecter les vulnérabilités susceptibles de provoquer une injection SQL. Cette pratique vous permet de traiter les risques de sécurité potentiels pendant le développement et de réduire le risque que des attaquants s’en prennent à votre application.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.



